Проблема с использованием 'eval' для определения функции верхнего уровня при вызове из объекта - PullRequest
3 голосов
/ 08 июня 2010

Я написал (в JavaScript) интерактивный цикл read-eval-print-loop, который инкапсулирован в объекте.Однако недавно я заметил, что определения функций верхнего уровня, указанные для интерпретатора, по-видимому, не запоминаются интерпретатором.После некоторой диагностической работы я сократил основную проблему до этого:

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

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

На данный момент я надеюсь, что следующие два утверждения будут работать, как и ожидалось:

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

Вместо этого я получаю ожидаемое значение для строки t1, а строка t2 завершается с ошибкой, что t2 не связан.

IOW: после запуска этого сценария у меня есть определение дляt1, и нет определения для t2.Акт вызова eval из evaler достаточно отличается от вызова верхнего уровня, так как глобальное определение не записывается.Что происходит, так это то, что вызов evaler.eval возвращает функциональный объект, поэтому я предполагаю, что t2 определяется и сохраняется в некотором другом наборе привязок, к которому у меня нет доступа.(Он не определен как член в evaler.)

Есть ли какое-нибудь простое решение для этого?Я перепробовал все виды исправлений и не наткнулся на один, который работает.(Большая часть того, что я сделал, была сосредоточена на том, чтобы перевести вызов eval в анонимную функцию и изменить способ вызова, цепочку __parent__ и т. Д.)

Есть мысли о том, как это исправить?

Вот результат немного большего исследования:


tl; dr: Rhino добавляет промежуточную область видимости в цепочку областей видимости при вызове метода в экземпляре.t2 определяется в этой промежуточной области, которая немедленно отбрасывается.@Matt: Ваш «хакерский» подход вполне может быть лучшим способом решения этой проблемы.

Я все еще делаю некоторую работу над первопричиной, но благодаря некоторому качественному времени с jdb я теперь лучше понимаюо том, что происходит.Как уже говорилось, оператор функции типа function t1() { return 42; } делает две вещи.

  • Создает анонимный экземпляр функционального объекта, как вы можете получить с выражением function() { return 42; }
  • Он связывает эту анонимную функцию с текущей верхней областью с именем t1.

Мой первоначальный вопрос о том, почему я не вижу, как происходит второе из этих событий, когда я вызываю eval из метода объекта.

код, который фактически выполняет привязку в Rhino, кажется, находится в функции org.mozilla.javascript.ScriptRuntime.initFunction.

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

Для приведенного выше случая t1, scope - это то, что я установил как область видимости верхнего уровня.Здесь я хочу, чтобы мои функции верхнего уровня были определены, так что это ожидаемый результат:

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

Однако, в случае t2, scope - это совсем другое:

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

И родительский объем этого NativeCall - это мой ожидаемый уровень верхнего уровня:

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

Это более или менее то, чего я боялся, когда писал это выше: «В случае прямого вычисления.t2 связывается в глобальной среде. В случае evaler он связывается «в другом месте». В этом случае «в другом месте» оказывается экземпляром NativeCall ... создается функция t2,привязанный к переменной t2 в NativeCall, и NativeCall исчезает, когда возвращается вызов evaler.eval.

И вот тут все становится немного неясным ... У меня нетЯ сделал столько анализа, сколько мне хотелось бы, но моя текущая рабочая теория заключается в том, что область действия NativeCall необходима для того, чтобы this указывал на evaler при выполнении в вызове evaler.eval.(Немного резервируя кадр стека, NativeCall добавляется в цепочку областей действия на Interpreter.initFrame, когда функция «нуждается в активации» и имеет ненулевой тип функции. Я предполагаю, что эти вещи верны для простоготолько вызовы функций, но они недостаточно прослежены, чтобы знать наверняка. Может быть, завтра.)

Ответы [ 5 ]

3 голосов
/ 08 июня 2010

Ваш код на самом деле вообще не работает. eval возвращает function, который вы никогда не вызываете.

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

Чтобы объяснить это немного подробнее:

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

EDIT

В ответ на:

Есть ли другой способ создать интерактивный цикл read-eval-print-loop, чем использовать eval?

Так как вы используете Rhino .. Я думаю, вы могли бы вызвать Rhino с объектом Java-процесса, чтобы прочитать файл с js?

Допустим, у меня есть этот файл:

test.js

function tf2() {
  return 2;
}

print(tf2());

Тогда я мог бы запустить этот код, который вызывает Rhino для оценки этого файла:

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

Таким образом, вы можете сделать еще один шаг, написав некоторый код для преобразования в файл, затем вызвав вышеуказанный код ...

Да, это смешно.

1 голос
/ 08 июня 2010

Проблема, с которой вы сталкиваетесь, заключается в том, что JavaScript использует определение уровня функции.

Когда вы вызываете eval() из функции eval, которую вы определили, возможно, она создает функцию t2()область действия этой eval: function(str) {} функции.

Вы можете использовать evaler.eval('global.t2 = function() { return 2; }'); t2();

Вы также можете сделать что-то вроде этого:

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

Или ....

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

Еще один шаг вперед:

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

С DOM вы можете обойти эту проблему определения уровня функций, введя код сценария «корневой уровень» вместо использования eval(),Вы должны создать тег <script>, задать для его текста код, который вы хотите оценить, и добавить его куда-нибудь в DOM.

0 голосов
/ 09 июня 2010

Я получил этот ответ из списка рассылки Rhino, и он, кажется, работает.

var window = this;

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

Ключ в том, что call явно устанавливает this, и это t2 определяется вправильное место.

0 голосов
/ 08 июня 2010

Я думаю, что это утверждение:

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

не объявляет функцию t2, она просто возвращает Function объект (это не function declaration, это function operator), поскольку он используется внутри выражения.

Поскольку оценка происходит внутри функции, область действия вновь созданной функции ограничена областью evaler.eval (т.е. вы можете использовать функцию t2 только из функции 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
0 голосов
/ 08 июня 2010

Возможно ли, что имя вашей функции "eval" сталкивается с самой функцией eval?Попробуйте это:

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

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

Редактировать
Я изменил, чтобы использовать предложение @ Matt и протестировал.Это работает как задумано.

Это хорошо?Я нахмурился на eval, лично.Но это работает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...