3 votes

Problèmes liés à l'utilisation de "eval" pour définir une fonction de niveau supérieur lorsqu'elle est appelée à partir d'un objet

J'ai écrit (en JavaScript) une boucle interactive de lecture-évaluation-imputation qui est encapsulée dans le logiciel dans un objet. Cependant, j'ai récemment remarqué que les définitions de fonctions de haut niveau spécifiées à l'interpréteur ne semblent pas être "mémorisées" par l'interpréteur. Après quelques travaux de diagnostic, j'ai réduit le problème principal à ceci :

   var evaler = {
     eval: function (str)
     {
       return eval(str);
     },
   };

   eval("function t1() { return 1; }");         // GOOD
   evaler.eval("function t2() { return 2; }");  // FAIL

À ce stade, j'espère que les deux déclarations suivantes fonctionneront comme prévu :

print(t1());     // => Results in 1 (This works)
print(t2());     // => Results in 2 (this fails with an error that t2 is undefined.)

Ce que j'obtiens à la place, c'est la valeur attendue pour l'élément t1 et la ligne t2 échoue avec l'erreur suivante t2 n'est pas liée.

IOW : Après avoir exécuté ce script, j'ai une définition pour t1 et aucune définition pour t2 . L'acte d'appeler l'approbation de l'intérieur evaler est suffisamment différent de l'appel au niveau supérieur pour que la définition globale ne soit pas enregistrée. Ce qui se passe, c'est que l'appel à evaler.eval renvoie un objet fonction, je suppose donc que t2 est défini et stocké dans un autre ensemble de liens auxquels je n'ai pas accès. (Il n'est pas défini en tant que membre de evaler .)

Existe-t-il une solution simple pour résoudre ce problème ? J'ai essayé toutes sortes de solutions, mais je n'en ai pas trouvé une qui fonctionne. (La plupart de mes tentatives ont consisté à placer l'appel à eval dans une fonction anonyme, et à modifier la façon dont elle est appelée, en changeant la fonction __parent__ etc.)

Des idées sur la manière de résoudre ce problème ?

Voici le résultat d'une enquête plus approfondie :


tl;dr : Rhino ajoute un scope intermédiaire à la chaîne de scope lors de l'appel d'une méthode sur une instance. t2 est en cours de définition dans cette portée intermédiaire, qui est immédiatement rejetée. @Matt : Votre approche "hacky" pourrait bien être la meilleure façon de résoudre ce problème.

Je travaille encore sur la cause première, mais grâce au temps passé avec jdb, j'ai maintenant une meilleure compréhension de ce qui se passe. Comme cela a été discuté, une déclaration de fonction comme function t1() { return 42; } fait deux choses.

  • Il crée une instance anonyme d'un objet fonction, comme vous l'obtiendriez avec l'expression function() { return 42; }
  • Il lie cette fonction anonyme à la portée supérieure actuelle avec le nom t1 .

Ma question initiale est de savoir pourquoi je ne vois pas la seconde de ces choses se produire lorsque j'appelle eval à partir d'une méthode d'un objet.

Le code qui effectue la liaison dans Rhino semble se trouver dans la fonction org.mozilla.javascript.ScriptRuntime.initFunction .

    if (type == FunctionNode.FUNCTION_STATEMENT) {
         ....
                scope.put(name, scope, function);

Pour les t1 cas ci-dessus, scope est ce que j'ai défini comme étant mon champ d'application de premier niveau. C'est là que je veux que mes fonctions de premier niveau soient définies, c'est donc un résultat attendu :

main[1] print function.getFunctionName()
 function.getFunctionName() = "t1"
main[1] print scope
 scope = "com.me.testprogram.Main@24148662"

Toutefois, dans la t2 cas, scope est tout à fait différent :

main[1] print function.getFunctionName()
 function.getFunctionName() = "t2"
main[1] print scope
 scope = "org.mozilla.javascript.NativeCall@23abcc03"

Et c'est le champ d'application parent de ce NativeCall c'est le champ d'application que j'attends au plus haut niveau :

main[1] print scope.getParentScope()
 scope.getParentScope() = "com.me.testprogram.Main@24148662"

C'est plus ou moins ce que je craignais en écrivant ce qui précède : "Dans le cas de l'évaluation directe, t2 est lié à l'environnement global. Dans le cas de l'évaluateur, il est lié 'ailleurs'" Dans ce cas, 'ailleurs' s'avère être l'instance de NativeCall ... le t2 est créée, liée à une fonction t2 dans la variable NativeCall et le NativeCall disparaît lorsque l'appel à evaler.eval les retours.

Et c'est là que les choses deviennent un peu floues... Je n'ai pas fait autant d'analyses que je le souhaiterais, mais ma théorie de travail actuelle est que le NativeCall Le champ d'application est nécessaire pour garantir que this pointe vers evaler lors de l'exécution de l'appel à evaler.eval . (Si l'on recule un peu dans le temps, la NativeCall est ajouté à la chaîne de portée par Interpreter.initFrame lorsque la fonction "a besoin d'être activée" et que son type de fonction n'est pas nul. Je suppose que ces choses ne sont vraies que pour les invocations de fonctions simples, mais je n'ai pas suffisamment remonté le fil du temps pour en être sûr. Peut-être demain).

3voto

Matt Points 21690

Votre code n'est pas du tout défaillant. Les eval renvoie un function que vous n'invoquez jamais.

print(evaler.eval("function t2() { return 2; }")()); // prints 2

Pour être un peu plus précis :

x = evaler.eval("function t2() { return 2; }"); // this returns a function
y = x(); // this invokes it, and saves the return value
print(y); // this prints the result

EDITAR

En réponse à :

Existe-t-il un autre moyen de créer une boucle interactive read-eval-print-loop que d'utiliser eval ?

Puisque vous utilisez Rhino Je suppose que vous pourriez appeler Rhino avec un objet Process java pour lire un fichier avec js ?

Disons que j'ai ce fichier :

test.js

function tf2() {
  return 2;
}

print(tf2());

Je pourrais ensuite exécuter ce code, qui appelle Rhino pour évaluer ce fichier :

process = java.lang.Runtime.getRuntime().exec('java -jar js.jar test.js');
result = java.io.BufferedReader(java.io.InputStreamReader(process.getInputStream()));
print(result.readLine()); // prints 2, believe it or not

Vous pouvez donc aller plus loin en écrivant du code à évaluer dans un fichier, puis en appelant le code ci-dessus ...

Oui, c'est ridicule.

1voto

gnarf Points 49213

Le problème que vous rencontrez est que JavaScript utilise un cadrage au niveau de la fonction.

Lorsque vous appelez eval() à partir de l'intérieur de la eval que vous avez définie, il est probablement en train de créer la fonction t2() dans le cadre de cette eval: function(str) {} fonction.

Vous pouvez utiliser evaler.eval('global.t2 = function() { return 2; }'); t2();

Vous pouvez également faire quelque chose comme ceci :

t2 = evaler.eval("function t2() { return 2; }");
t2();

Ou....

var someFunc = evaler.eval("function t2() { return 2; }");
// if we got a "named" function, lets drop it into our namespace:
if (someFunc.name) this[someFunc.name] = someFunc;
// now lets try calling it?
t2();
// returns 2

Il faut même aller plus loin :

var evaler = (function(global){
  return {
    eval: function (str)
    {
      var ret = eval(str);
      if (ret.name) global[ret.name] = ret;
      return ret;
    }
  };
})(this);

evaler.eval('function t2() { return 2; }');
t2(); // returns 2

Avec le DOM, vous pouvez contourner ce problème de délimitation des fonctions en injectant du code script au niveau de la racine au lieu d'utiliser eval() . Vous créerez un <script> et de définir son texte comme étant le code à évaluer, puis de l'ajouter quelque part dans le DOM.

0voto

Randolpho Points 36512

Est-il possible que le nom de votre fonction "eval" entre en collision avec la fonction eval elle-même ? Essayez ceci :

var evaler = {
  evalit: function (str)
  {
    return window.eval(str);
  },
};

eval("function t1() { return 1; }");
evaler.evalit("function t2() { return 2; }");

Editer
J'ai modifié l'utilisation de @Matt et testé. Cela fonctionne comme prévu.

Est-ce que c'est bon ? Je fronce les sourcils eval personnellement. Mais cela fonctionne.

0voto

el.pescado Points 7960

Je pense que cette déclaration :

evaler.eval("function t2() { return 2; }");

ne déclare pas la fonction t2 il renvoie simplement Function (ce n'est pas function declaration , c'est function operator ), car il est utilisé à l'intérieur d'une expression.

Comme l'évaluation se fait à l'intérieur d'une fonction, la portée de la fonction nouvellement créée est limitée à evaler.eval (c'est-à-dire que vous pouvez utiliser t2 uniquement à partir de evaler.eval ) :

js> function foo () {
eval ("function baz() { return 'baz'; }");
print (baz);
}
js> foo ();
function baz() {
    return "baz";
}
js> print(baz);
typein:36: ReferenceError: baz is not defined

0voto

mschaef Points 671

J'ai obtenu cette réponse sur la liste de diffusion de Rhino, et cela semble fonctionner.

var window = this;

var evaler = {
    eval : function (str) {
         eval.call(window, str);
    }
};

L'essentiel est que call définit explicitement this , ce qui permet d'obtenir t2 défini au bon endroit.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X