par , 26/04/2015 à 22h36 (2222 Affichages)
Lors de mon précédent billet, j'avais présenté la fonction eval() et montré en quoi il était peu recommandé de l’utiliser.
Je vais montrer dans ce billet un moyen d’interpréter du code JavaScript sans utiliser eval(), mais avant cela, essayons de voir les cas d'utilisation où l'interprétation de scripts peut s'avérer nécessaire.
Faire appel à des scripts au sein d'une application n'est pas nécessairement un besoin très courant dans la mise en œuvre d'un site web ou même de la plupart des applications que l'on peut rencontrer sur la toile.
Cependant, cela peut s'avérer utile voire nécessaire lorsqu'interviennent des règles pouvant être ajoutées (ou retirées) à postériori de la phase de développement, où lorsque tout simplement il est plus simple ou plus rapide d'implémenter ces règles à travers des scripts plutôt qu'en travaillant sur l'application en elle-même.
Le cas le plus typique et le plus parlant est probablement le jeu vidéo, où il est très fréquent de faire appel à du scripting pour gérer soit des règles très spécifiques et pointues comme l'intelligence artificielle qui font souvent appel à un/des développeur/s dédié/s, ou pour faciliter les développements en fournissant aux développeurs un moyen plus simple d'étendre les fonctionnalités du jeu sans avoir à modifier (et donc à re-tester) le cœur du programme.
Les jeux développés en langage C ou C++ font assez régulièrement appel au langage Lua pour le scripting, plus pour des raisons d'habitudes que pour les qualités intrinsèques de Lua disons-le. Mais avec l'avènement du HTML5 et des jeux online, le choix d'utiliser JavaScript comme langage de scripting, d'autant plus qu'il devient de plus en plus populaire côté serveur avec node.js, est tout à fait envisageable, à condition de pouvoir interpréter du code JavaScript de façon fiable, donc sans faire appel à la primitive eval().
On cherche en fait à pouvoir interpréter du code JavaScript dans une sorte d'environnement protégé, on parle souvent de sandboxing (bac à sable) à ce propos.
Pour cela, le seul moyen n'est ni plus ni moins d'employer un interpréteur JavaScript externe qui nous permettra de lancer des scripts qui n'auront pas accès aux variables globales du programme appelant.
On ne veut donc surtout pas ici que l'interprétation se comporte comme une fermeture (closure), comme c'est le cas de la primitive eval() en mode strict comme vu dans la partie précédente.
Il existe assez peu d'interpréteurs JavaScript implémentés eux-même en JavaScript. Il faut croire que la technologie HTML5 doit encore faire ses preuves dans le domaine vidéo-ludique, mais c'est un autre sujet.
On peut trouver actuellement trois interpréteurs JavaScript open source écrits en JavaScript :
L'interpréteur le plus simple à appréhender des trois est le premier, JS Interpreter, réalisé par un développeur de chez Google, bien que le projet ne soit pas officiellement un projet Google.
Son utilisation est relativement directe comme le montre l'exemple ci-dessous :
1 2 3 4
| var myCode = 'var a=1; for(var i=0;i<4;i++){a*=i;} a;';
var myInterpreter = new Interpreter(myCode);
myInterpreter.run();
var result = myInterpreter.value; |
Le code JavaScript interprété est ainsi totalement isolé du programme appelant et ne peut pas par exemple modifier des variables globales du programme appelant contrairement à eval().
Cependant, il peut être utile et même nécessaire de rendre accessible, d'exposer, certaines variables ou fonctions du programme appelant au script interprété.
JS Interpreter le permet à l'aide d'un paramètre optionnel, une fonction d'initialisation du contexte, à passer au constructeur de l'interpréteur.
1 2 3 4 5 6 7 8 9 10 11 12
| function square(x) {
return x * x;
}
var initFunc = function(interpreter, scope) {
var wrapper = function(x) {
return interpreter.createPrimitive(square(x));
};
interpreter.setProperty(scope, 'square', interpreter.createNativeFunction(wrapper));
};
var myInterpreter = new Interpreter('var y = square(5)', initFunc); |
Ainsi, nous avons à la fois un environnement JavaScript isolé du programme appelant, tout en ayant la possibilité d'exposer explicitement certaines fonctions, square() dans l'exemple ci-dessus, du programme appelant dans le script.
Nous pouvons donc définir dans le programme appelant un ensemble de fonctions accessible aux scripts formant de la sorte ce qui est commun d'appeler une API de scripting.
A noter tout de même, en bémol, que l'interpréteur JS Interpreter, en l'état, est très basique. Il vous faudra sans doute le modifier et l'adapter pour qu'il puisse être utilisé dans le cadre d'un véritable environnement de scripting, mais c'est déjà un bon début.
Les deux autres interpréteurs JavaScript mentionnés plus haut manquent quand à eux d'une documentation claire et complète pour en saisir facilement toutes les fonctionnalités. Ce qui est assez dommage.
Enfin, il est à regretter que ces trois projets ne semblent plus être réellement maintenus au moment où j'écris ces lignes.
A l'heure où le développement de jeux Web utilisant la technologie HTML5 devient de plus en plus répandu, il reste surprenant qu'il y ait un tel déficit au niveau des interpréteurs JavaScript écrits en JavaScript.
Il pourrait être judicieux par conséquent, c'est une réflexion en cours pour ma part, de forker le projet JS Interpreter pour développer ses fonctionnalités, avec notamment le support de la norme ECMAScript 5 à minima voire d'ECMAScript 6 idéalement.
A ceux que ce projet pourrait intéresser, qu'ils n'hésitent pas à me contacter en message privé.