P
'
t
i
t
e
C
h
a
t
t
e
 
spacer~ DON'T BLAME ME, IT'S A SOFTWARE PROBLEM Articles | Connexion
 
~Le motif Interpréteur

 Présentation

Bien que les interfaces graphiques aient réllement pris le pas sur les outils en mode texte auprès du grand public, beaucoup de programmes proposent encore des fonctionnalités destinées aux utilisateurs dits "avancés". Notamment la possibilité de piloter le programme grâce à un langage.
 Sommaire


 Introduction

Certains programmes bénéficient d'un langage interne qui permet de décrire les opérations qu'ils peuvent effectuer. Le modèle de conception objet Interpréteur définit une représentation de la grammaire d'un tel langage, ainsi qu'un interpréteur utilisant cette représentation pour en manipuler les phrases.

Lorsqu'une application présente de nombreux cas d'utilisation différents, mais relativement similaires, il peut se révéler avantageux de faire appel à un langage simple pour décrire ces situations. Nombreux sont les logiciels connus disposant d'un tel outil. Nous pouvons par exemple citer les suites bureautiques et leurs langages de macro. Il s'agit ici d'une implémentation relativement simple. D'autres outils peuvent intégrer des langages bien plus complexes tel que Emacs ou encore tous les programmes utilisant l'interpréteur Python pour Java. Les éditeurs de texte et leurs expressions rationnelles ne sont pas en reste.

La plus grosse difficulté concernant la mise en place de ce Design Pattern concerne la décision de son application. Typiquement, nous pouvons distinguer deux situations dans lesquelles l'intérêt de notre motif apparaît évident : quand nous devons traiter des expressions mathématiques (les tableurs en sont l'exemple le plus probant) et quand nous devons afficher les données suivant plusieurs manières. Dans ce dernier cas, il s'agira par exemple d'un générateur de rapports qui proposera une sortie triée du contenu en fonction de critères pouvant varier au gré des désirs de l'utilisateur.


 Structure

Le modèle Interpréteur utilise des classes pour représenter les règles de sa grammaire. Notre exemple repose sur un langage d'expressions mathématiques simples. Celui-ci sera intégré à une application de gestion de notes pour des élèves. La présence de notre langage permettra à l'utilisateur de modifier facilement toutes les notes de façon homogène, en leur ajoutant toutes 0.5 point par exemple. Le code source de l'interpréteur se trouve sur le CD-Rom accompagnant ce magazine, dans le répertoire ./noteseditor/src/org/jext/calculator. Le listing 2 propose une exemple de son utilisation.

#Listing 2
CalculatorParser p = new CalculatorParser();
Atom arbre = p.compute(chaine);
double resultat = arbre.getValue();
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
La grammaire de ce langage se trouve décrite dans le listing 1. Les opérateurs sont les opérateurs mathématiques classiques, à l'exception de < et de > qui représentent les opérations de décalage binaire vers la gauche et la droite. Nous n'employons ici qu'un seul caractère dans le but de faciliter l'implémentation.

#Listing 1
operator ::= [+-*/%=<>]
variable ::= [a-zA-Z]
number ::= [0-9]*(\.[0-9]+)?
atom ::= variable | number | expression
expression ::= atom operator atom
       
      
JextCopier dans Jext
Notre logiciel définira donc les classes Variable, Number, Expression, Operator et l'interface Atom. Chaque expression décrite par cette grammaire sera représentée dans le code source par un arbre syntaxique abstrait composé d'instances de ces classes. Le schéma 1 présente un exemple simplifié d'un tel arbre. Chacune d'entre elles proposera la méthode getValue() dont le rôle sera d'interpréter la valeur du sous-arbre dépendant d'une instance donnée.



Schéma 1. Arbre syntaxique de l'expression 2 + 3 * n.

Dans le cas du calcul 2 * 3, nous avons une instance d'Expression composée de deux instances de Number et d'une instance d'Operator. Lors de l'invocation de expression.getValue() nous allons appeler la méthode compute() de l'opérateur. Celle-ci réalise un calcul mathématique en fonction des valeurs renvoyées par les méthodes getValue() de ses opérandes. Pour un nombre, il s'agit simplement de sa représentation numérique. Si d'aventure une expression se substitue à un nombre en guise d'opérande, l'appel provoquerait une nouvelle invocation de compute(). De cette manière, l'exécution de getValue() sur l'objet racine de l'arbre entraînera l'interprétation de l'ensemble automatiquement.

Une telle structure peut se généraliser sous la forme décrite par le schéma 2. Dans ce nouvel arbre nous distinguons différents constituants. Le client construit un arbre syntaxique abstrait à partir d'une phrase du langage. Ce constituant correspond dans notre exemple à la méthode compute() de la classe CalculatorParser (dans le paquetage org.jext.calculator ).



Schéma 2. Généralisation de la structure du motif Interpréteur.

Le contexte pour sa part définit une information à caractère global pour l'interpréteur. Notre calculatrice utilisera ainsi pour contexte un tableau de hachage contenant 26 variables nommées de A à Z. L'expression abstraite déclare l'opération abstraite de traitement commune à tous les noeuds de l'arbre. Notre implémentation utilise pour cela l'interface Atom dont l'unique méthode getValue() joue le rôle de cette opération. Une expression terminale correspond nécessairement aux feuilles de l'abre et agit comme condition d'arrêt de la récursion lors de l'interprétation. Dans une expression mathématique, ce type d'expression se voit représentée par des nombres et des variables. Pour terminer, les expressions non terminales autorisent l'évaluation récursive de la phrase.

Chaque élement de la grammaire (un élément se trouve défini par Element ::= ...) doit correspondre à une classe de ce type. Ces dernières doivent encapsuler une instance d'expression abstraite par symbole caractéristique. Puisque notre grammaire défninit l'élément expression ::= atom operator atom, la classe Expression encapsulera donc trois instances abstraites.


 Avantages et inconvénients

L'implémentation du design pattern Interpréteur se révèle véritablement aisée. En premier lieu, l'extension de la grammaire ne demande que très peu d'efforts puisque chaque règle se trouve représentée par une classe propre. Par héritage des anciennes règles, le développeur sera en mesure d'enrichir son langage. Par ailleurs, la rédaction des classes se révèle particulièrement simple. Le code ne variant la plupart du temps que très peu de l'une à l'autre.

Malheureusement, dès que le langage atteint un niveau de complexité trop important, la maintenance du motif devient malaisée. Si le fait de définir une classe par règle rend la transition grammaire/implémentation rapide, un trop grand nombre d'entre elles rend l'architecture du code source bien trop lourde. Dans ce cas, on pourra se tourner vers des outils de génération automatique d'analyseurs syntaxiques. En outre, une telle implémentation n'offre pas de performances intéressantes. Le rôle de ce motif est de résoudre de petits problèmes relativement simples. Un véritable interpréteur ne pourrait se satisfaire de la seule évaluation de l'arbre. L'exécution passerait par la traduction de ce dernier sous une autre forme, plus adaptée à des traitements lourds.

Certains autres motifs de conceptions peuvent se voir utilisés conjointement à celui-ci pour améliorer le traitement. Nous retiendrons essentiellement les motifs Visiteur et Poids Mouche.

Le premier servira à implémenter de nouveaux modes d'interprétation du langage. Notre interpréteur pourrait de la sorte évaluer une expression mais aussi en vérifier simplement la conformité, sans effectuer le moindre calcul mathématique. Le second trouvera sa place dans la gestion des expressions terminales. Dans notre application, une même variable pourra apparaître plusieurs fois au sein d'un même calcul. L'état extrinsèque dépendra alors du contexte tandis que l'état intrinsèque (le nom de la variable) restera le même, et le motif Poids Mouche s'appliquera alors.


 Téléchargements

Vous pouvez télécharger l'exemple complet.



par Romain Guy
romain.guy@jext.org
http://www.jext.org
Dernière mise à jour : 14/10/2006



 
#ProgX©2005 Mathieu GINOD - Romain GUY - Erik LOUISE