3

我已经(在 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:您的“hacky”方法很可能是解决此问题的最佳方法。

我仍在为根本原因做一些工作,但是由于在 jdb 上度过了一段美好的时光,我现在对正在发生的事情有了更多的了解。如前所述,函数语句 likefunction 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"

这或多或少是我在上面写这个时所害怕的:“在直接 eval 的情况下,t2 被绑定在全局环境中。在 evaler 的情况下,它被绑定在 'elsewhere'” 在这种情况下,'elsewhere ' 结果是NativeCall... 的实例,该t2函数被创建,绑定到 中的一个t2变量NativeCall,并且当调用返回NativeCall时消失。evaler.eval

这就是事情变得有点模糊的地方......我没有做尽可能多的分析,但我目前的工作理论是需要范围来NativeCall确保在调用. (稍微备份堆栈帧,当函数“需要激活”并且具有非零函数类型时,它们被添加到作用域链中。我假设这些事情仅适用于简单的函数调用,但没有'没有追踪到足够确定的上游。也许明天。)thisevalerevaler.evalNativeCallInterpreter.initFrame

4

5 回答 5

3

您的代码实际上根本没有失败。eval正在返回function您从未调用过的a 。

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

编辑

回应:

除了使用 eval 之外,还有另一种创建交互式读取评估打印循环的方法吗?

由于您使用的是 Rhino.. 我想您可以使用 java Process 对象调用 Rhino 来使用 js 读取文件?

假设我有这个文件:

测试.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

所以你可以更进一步,通过编写一些代码来评估文件,然后调用上面的代码......

是的,这很荒谬。

于 2010-06-08T16:39:11.073 回答
1

您遇到的问题是 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 某处。

于 2010-06-08T17:22:33.970 回答
0

您的函数名称“eval”是否可能与 eval 函数本身发生冲突?试试这个:

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

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

编辑
我修改为使用@Matt的建议并进行了测试。这按预期工作。

好吗?我个人皱着眉头eval。但它有效。

于 2010-06-08T16:23:43.733 回答
0

我认为这种说法:

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
于 2010-06-08T16:30:28.760 回答
0

我从 Rhino 邮件列表中得到了这个答案,它似乎有效。

var window = this;

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

关键是call显式设置this,并且t2在适当的位置定义。

于 2010-06-09T14:55:30.583 回答