Я написал (в 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
, когда функция «нуждается в активации» и имеет ненулевой тип функции. Я предполагаю, что эти вещи верны для простоготолько вызовы функций, но они недостаточно прослежены, чтобы знать наверняка. Может быть, завтра.)