:: Enseignements :: ESIPE :: E4INFO :: 2011-2012 :: Génération de code ::
![[LOGO]](http://igm.univ-mlv.fr/ens/resources/mlv.png) | Projet - Partie 1 |
Première partie du projet, modification de la grammaire, implantation d'un vérificateur de type.
Avant propos
Le but de ce projet est à la fin d'avoir écrit un interpreteur ainsi qu'un compilateur vers
le bytecode Java pour un petit language simple.
Cette année, le langage s'appele
ir2012 pour faire original. Sa syntaxe est plus proche de celle de Python
ou Ruby que de celle du C ou de Java. Sa sémantique, elle plus proche d'un C--, donc c'est un langage statiquement
typé (3 types possibles
bool,
int et
string), des fonctions, des tests, des boucles, des variables.
Le langage utilise deux astuces pour que sa syntaxe ressemble à du Python, comme avec les anciens C (K&R) si l'on ne declare
pas le type d'une variable, d'un paramètre ou d'un type de retour, celui-ci sera implicitement déclaré comme un entier.
De plus, au lieu d'utiliser l'indentation pour trouver les fins de bloc d'instruction comme en Python
car cela requiert soit beaucoup de larmes soit d'écrire son parseur à la main, le language considèrera les
instructions
return,
break et
continue comme étant des fins de bloc d'instructions implicites
et le saut d'une ligne vide comme étant la fin d'une fonction (et oui cela veux dire que si l'on saute une ligne
pour aérer le programme ne compilera plus, comme cela tout le monde va être obligé d'écrire le même code, au saut
de ligne près).
Un script est un ensemble de fonctions suivi d'un ensemble d'instructions. La première intruction de l'ensemble
d'instructions sera la première instruction exécuté lors du lancement du script.
Contrairement au C, l'ordre de déclaration des fonctions n'a pas d'importance (comme en Java).
Par exemple, un hello world s'écrit comme ceci:
print "Hello World !"
Une fonction est déclarée en utilisant le mot-clé
def suivi de son nom, des paramètres entre parenthèse
puis d'un bloc d'instruction qui doit commencer par le caractère ':' et se finir soit par
end
soit par
return,
break et
continue soit par un aut d'une ligne vide.
def fibo(n):
if n < 2:
return 1
return fibo(n - 1) + fibo(n - 2)
print fibo(7)
A l'intérieur d'un bloc on écrit des instructions, celle-ci se termine soit par un retour
à la ligne soit par un point virgule (';') si l'on veut mettre plusieurs intructions sur une ligne.
Il existe 6 instructions :
-
La déclaration de variable qui se fait en utilisant le mot clé var.
var a = 7
Une variable est forcément initialisée lors de sa déclaration (et en passant null n'existe pas).
Il est possible de déclarer le type de la variable (implicitement le type est int)
en indiquant le type entre var et le nom de la variable.
var int a = 7
-
L'affectation d'une variable
a = 42
dans ce cas, la variable devra avoir été déclarée au préalable (et avec le bon type).
Noté que l'assignation est une instruction donc contrairement au C, ce code n'est pas valide
a = b = c .
-
Le test if ... elif ... else. elif est l'équivalent d'un 'else if'
comme en Python (notez que elif s'appele elseif en PHP et elsif en Ruby.
if value < 10:
print "small"
elif value < 20:
print "medium"
else:
print "large"
end
Notez de plus, que dans l'exemple si au lieu de print "large" l'instruction était un
return, break ou continue, le end pour finir le bloc
aurait été superflue.
-
La boucle for qui est composé de 3 partie optionelle séparée par des virgules
(les virgules ne sont pas optionelles !) suivi d'un bloc d'instructions.
La première partie est une déclaration ou une affectation.
La seconde partie est une expression booléen, la boucle torunera tant que l'expression sera vraie.
La troisième partie est une affectation ou une expression.
La semantique de la boucle est la même que celle du C.
var sum = 0
for var i = 0, i < n, i = i + 1:
sum = sum + i
end
De plus, à l'intérieur du corps de la boucle, l'instruction break permet de sortir
de la boucle (contrairement à Java, il n'est pas possible d'utiliser des labels pour les boucles)
et l'instruction continue permet de sauter la fin du corps de la boucle pour atterir
juste avant l'exécution de la troisième partie de la boucle.
-
Les appels de fonctions.
Dans ce cas, la valeur de retour de la fonction sera ignoré.
-
La fonction built-in print qui permet d'afficher n'importe quelle expression
sur la console.
Les expressions sont soit des constantes (true, false, 42, "hello"), soit des variables (a, b, foo),
soit des expressions unaire (+ et -), soit des expressions binaires de calcul (+, -, *, /, %), soit des tests d'égalités
(== et !=), soit des opérateurs de comparaison (<=, <, >, >=), soit des appels de fonction, soit les 3 fonctions
built-in scan_bool,
scan_int et
scan_string.
Les expressions unaires, les expressions binaires de calcul et les opérateurs de comparaison ne s'appliquent
que sur les entiers. Les opérateurs == et != requiert que les deux opérandes est le même type.
Le fichier ZIP contient tout ce qu'il faut pour démarrer:
ir2012-td3.zip,
il suffit de le dézipper dans votre répertoire workspace de eclipse puis de créer un nouveau projet nommé
ir2012, dans ce cas eclipse verra qu'il existeb déjà :)
Le code contenu servira de base pour les exercices 1 & 2, le fichier ant permettant d'utiliser Tatoo
est rangé dans le répertoire
lab1 ainsi que la description de la grammaire du langage
ir2012.ebnf.
Les sources sont stockés dans
lab1/src et les sources générées dans
lab1/gen-src.
Un petit ensemble d'exemples est fourni dans le répertoire
samples.
De plus ain de vous aidez, il existe dans le répertoire
lib un jar exécutable
nommé
typechecker-slim.jar qui implante déjà un typechecker.
Cela vous permettra de tester la sortie de votre programme par rapport à la sortie de ce jar exécutable.
Pour exécuter le jar:
java -jar lib/typechecker-slim.jar samples/mon_super_samples.ir2012
Exercice 1 - Modification de la grammaire
-
Tester que la grammaire actuelle permet de déclarer une variable et d'appeler print sur la variable.
Pour cela, executer la commande java -cp lab1/classes:lib/tatoo-runtime.jar fr.umlv.ir2012.GrammarTester et
écriver votre script sur l'entrée standard en terminant par un Ctrl-D (CTRL-Z sous Windows).
Vous pouvez aussi écrire votre script dans un fichier texte et passer celui-ci en paramètre
de la même commande.
-
Modifier l'expression regulière correspondant au nom des variables (et des méthodes) pour accepter
autre chose que 'a'. Disons, qu'un identifiants doit commencer par une lettre et être suivi
d'une lettre ou d'un chiffre et que '_' est considéré comme une lettre valide.
Autre précisions, on accepte les majuscules et les minuscules.
-
Vérifier si les priorités/associativités des opérateurs sont les bonnes.
Indice, regarder 1 + 2 * 4.
-
Le langage permet de nommer les paramètres lors d'un appel de fonction,
écrivez un exemple d'appel de fonction avec des paramètres nommés.
-
Un Mogwai a tapé sur mon clavier et supprimé les productions correspondant à l'instruction for
de la grammaire, réparez ces bétises en ajoutant les productions pour que les exemples marchent.
Exercice 2 - Implantation d'un type-checker
Le but est d'écrire un visiteur qui va vérifier si le code est bien typé, que les variables
sot bien déclarés etc.
Concrètement, cela revient fournir une implantation à toute les méthodes visit du
visiteur nommé TypeChecker (là où il y a des TODOs) mais avant il faut comprendre un peu
le code et les classes annexes qui vous ont été founies.
Pour rendre la chose plus fun, j'ai enlevé les commentaires, à vous de les ajouter.
-
A quoi sert la classe Env ?
Noté que vous avez le doit de faire un CTRL+SHIFT+G sur le nom de la classe ou sur son constructeur
pour voir là où elle est utilisée.
Une fois que vous aurez trouver à quoi elle sert, ajouter un commentaire de documentation
indiquant juste à quoi elle sert.
-
A quoi servent les classes Symbol et SymbolTable ?
De la même façon, ajouter la documentation qu'il convient.
Rappel, on écrit toujours dans le commentaire de doc de la classe à quoi elle sert, quelle est sont
rôle et des les commentaires de doc des méthodes comment on utilise les méthodes.
-
Dans la classe TypeChecker, à quoi sert la méthode createFunctionNameToFunctionMap ?
Rappel 2: on ne veut pas savoir ce qu'elle fait mais à quoi elle sert.
-
A quoi sert le paramètre node de la méthode reportError ?
-
A quoi sert la méthode dispatch ?
Maintenant, vous devriez avoir une idée plus précise de la façon dont la classe
TypeChecker doit être implantée.
-
Implanter les méthodes visit pour les constantes ainsi que la méthode
visit pour print. tester avec un exemple simple.
-
Implanter la méthode visit pour les built-ins scan_bool,
scan_int et scan_string.
-
Implanter les visit pour la déclaration, l'affectation (assignment en anglais)
ainsi que la visit d'une expression contenant une variable.
N'oublier pas de tester.
-
Implanter les méthodes visit pour le if.
Noter que si le code de plusieurs visit est similaire, on a le droit de créer
des méthodes private :)
-
Vérifier que le code ci-dessous affiche bien 1 puis 2 puis 1.
var a = 1
if a == 1:
print a
var a = 2
print a
end
print a
-
Implanter les méthodes visit correspondant au moins et au plus unaire.
Tester avec un exemple simple.
-
Implanter les operations binaires.
Attention à bien respecter la sémantique du langage.
Vous pouvez tester avec le sample cascaded_if.ir2012.
-
Ajouter les visits correspondant à la boucle for ainsi
que pour les instructions break et continue.
-
Ajouter les visits correspondant à la déclaration de fonction
ainsi qu'à l'instruction return.
-
Enfin, ajouter la visit correspondant à l'appel de méthode.
Dans le cas des paramètres nommés, tester le code suivant:
def foo(a, b):
print a - b
foo(1, a: 2) // calls foo(2, 1)
foo(b: 1, a: 2) // calls foo(2, 1)
foo(b: 1, 2) // calls foo(2, 1)
foo(a:1, a:2) // error: duplicate argument name
foo(a:1, 1) // error: duplicate argument name
© Université de Marne-la-Vallée