我已经用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.eval返回一个函数对象,因此我假设正在定义t2并将其存储在我无法访问的其他绑定集中. (未将其定义为逃避程序的成员.)
有没有简单的解决办法?我尝试了各种修复方法,但没有偶然发现一种可行的方法. (我所做的大部分工作都集中在将对eval的调用放在一个匿名函数中,并更改了调用方式,将__parent__等链接起来.)
有关如何解决此问题的任何想法?
这是更多调查的结果:
tl; dr:在实例上调用方法时,Rhino将一个中间范围添加到范围链.在此中间范围内定义了t2,该范围立即被丢弃. @Matt:您的“ hacky”方法可能是解决此问题的最佳方法.
我仍然在根本原因上进行一些工作,但是由于使用了jdb花费了很多时间,我现在对发生的事情有了更多的了解.正如已经讨论过的,类似函数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案例,范围是我设置为*范围的内容.这是我要定义*功能的地方,因此这是预期的结果:
main[1] print function.getFunctionName()
function.getFunctionName() = "t1"
main[1] print scope
scope = "com.me.testprogram.Main@24148662"
但是,在t2情况下,范围完全是另外一回事:
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绑定在全球环境中.在逃避者的情况下,t2绑定在“其他地方””在这种情况下,“其他地方”原来是NativeCall的实例…创建了t2函数,绑定到NativeCall中的t2变量,当对evaler.eval的调用返回时,NativeCall消失.
这就是事情变得有点模糊的地方…我没有做我想做的很多分析,但是我目前的工作理论是需要NativeCall范围来确保在调用执行时可以避免这种情况逃避评估(稍微备份堆栈帧,当函数“需要激活”并且具有非零函数类型时,NativeCall将由Interpreter.initFrame添加到作用域链中.我假设这些对于简单的函数调用都是正确的仅,但还没有足够的溯源来确定(也许明天).
解决方法:
您的代码实际上根本没有失败.评估将返回您从未调用的函数.
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
编辑
回应:
Is there another way to create an interactive read-eval-print-loop than to use eval?
由于您使用的是Rhino.我想您可以使用Java Process对象调用Rhino以使用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
因此,您可以通过编写一些代码以评估到文件中,然后调用上面的代码来进一步执行此操作…
是的,这太荒谬了.