获取函数内的最后一个求值表达

时间:2014-05-28 18:33:29

标签: java javascript rhino abstract-syntax-tree

这与另一个问题有关:

Last evaluated expression in Javascript

但我想提供更多有关我想做的事情的细节,并展示我最终是如何解决这个问题,就像一些用户在评论中所要求的那样。

我有一些由我的应用用户编写的Javascript片段。这个片段需要转到这样的模板:

var foo1 = function(data, options) {
    <snippet of code written by user>  
}

var foo2 = function(data, options) {
    <snippet of code written by user>  
}

...

表达式可能会有很大不同,例如:

data.price * data.qty

对于这样更复杂的事情:

if (data.isExternal) {
    data.email;
} else {
    data.userId;
}

函数返回的值应始终是最后计算的表达式。

在我们有这样的事情之前:

var foo1 = function(data, options) {
    return eval(<snippet of code written by user>);
}

但是由于我们正在进行优化和更改,我们无法继续使用eval,但我们需要返回最后一次评估的表达式。

只需添加&#39;返回&#39;关键字不会起作用,因为表达式可以包含多个语句。所以我需要让这些函数返回最后评估的表达式。

限制和澄清:

  • 我无法强制用户添加&#39; return&#39;他们拥有的所有脚本的关键字,因为已经编写了许多脚本,对于简单的表达式,例如&#39; a * b&#39;。
  • 并不是非常直观。
  • 我使用Java和Rhino在服务器端运行Javascripts。

1 个答案:

答案 0 :(得分:0)

正如人们在Last evaluated expression in Javascript中指出的那样,在标准Javascript中无法获取最后一个评估表达式。

正如FelixKling所建议的那样,我最终做的就是操纵用户编写的脚本的AST。这样我就存储了用户编写的脚本和修改后的版本,这是我最终运行的版本。

为了操作AST,我使用了Rhino并基本上修改了所有EXPR_RESULT节点,以将结果存储在我最终在脚本末尾返回的变量中。以下是执行此操作的代码:

public class ScriptsManipulationService {     private static final Logger logger = LoggerFactory.getLogger(ScriptsManipulationService.class);

public String returnLastExpressionEvaluated(String script) {
    Parser jsParser = new Parser();
    try {
        AstRoot ast = jsParser.parse(script, "script", 1);
        ast.getType();
        ast.visitAll(new NodeVisitor() {
            @Override
            public boolean visit(AstNode node) {
                if (node.getType() == Token.EXPR_RESULT) {
                    ExpressionStatement exprNode = (ExpressionStatement) node;
                    Assignment assignmentNode = createAssignmentNode("_returnValue", exprNode.getExpression());
                    assignmentNode.setParent(exprNode);
                    exprNode.setExpression(assignmentNode);
                }
                return true;
            }
        });
        StringBuilder result = new StringBuilder();
        result.append("var _returnValue;\n");
        result.append(ast.toSource());
        result.append("return _returnValue;\n");
        return result.toString();
    } catch (Exception e) {
        logger.debug(LogUtils.format("Error parsing script"), e);
        return script;
    }
}

private Assignment createAssignmentNode(String varName, AstNode rightNode) {
    Assignment assignmentNode = new Assignment();
    assignmentNode.setType(Token.ASSIGN);
    Name leftNode = new Name();
    leftNode.setType(Token.NAME);
    leftNode.setIdentifier(varName);
    leftNode.setParent(assignmentNode);
    assignmentNode.setLeft(leftNode);
    rightNode.setParent(assignmentNode);
    assignmentNode.setRight(rightNode);
    return assignmentNode;
}

}

这样,如果您传递以下脚本:

data.price * data.qty;

你会回来的:

var _returnValue;
_returnValue = data.price * data.qty;
return _returnValue;

或者如果你通过:

var _returnValue;
if (data.isExternal) {
    _returnValue = data.email;
} else {
    _returnValue = data.userId;
}
return _returnValue;

请记住,我没有做过详尽的测试,并会随着时间的推移进行抛光,但这应该表明一般的想法。