如何在Nashorn中跟踪变量依赖关系?

时间:2018-07-31 14:35:07

标签: java nashorn

我想将Nashorn引擎用作通用计算引擎。它功能强大,速度快,并具有大量内置函数,并且使用@FunctionalInterfacestatic methods非常容易添加新功能。更好的是,它还提供了诸如循环依赖项检查,语法检查等增值功能。

但是,当依赖项更改时,我需要自动更新“输出”变量。

一般的想法是,在Java中,我会遇到类似的东西:

class CalculationEngine {
   Data addData(String name, Number value){
       ...
   }
   Data addData(String name, String formula){
       ...
   }
   String getScript(){
       ...
   }
}

CalculationEngine engine = new CalculationEngine();
Data datum1 = engine.addData("datum1", 1); // Constant integer 1
Data datum2 = engine.addData("datum2", 2); // Constant integer 2
Data datum3 = engine.addData("datum3", "datum1*10");
Data datum4 = engine.addData("datum4", "datum3+datum2");

CalculationEngine服务类知道如何使用Nashorn从Data对象中创建脚本字符串,如下所示:

final String script = engine.getScript(); // "var datum1=1; var datum2=2; var datum3=datum1*10; var datum4=datum3+datum2;"

我知道我可以使用Nashorn解析器来解析脚本:

final CompilationUnitTree tree = parser.parse("test", script, null);

但是我如何提取依赖项:

List<Data> whatDependsOn(Data input){
   // Process the parsed tree 
   return list;
}

使得whatDependsOn(datum2)返回[datum4],而whatDependsOn(datum1)返回[datum3,datum4]吗?

或反函数getReferencedVariables使得getReferencedVariables(datum3)返回[datum1],而getReferencedVariables(datum4)返回[datum2,datum3](我可以递归查询getReferencedVariables直到所有引用的变量已找到)。

基本上,当我的Data对象之一的“值”发生变化(由于外部事件)时,我如何确定哪些脚本公式受到影响并需要重新计算?

我知道Nashorn script can be parsed,但是我不知道如何使用SimpleTreeVisitorES6建立变量依赖图:

    final CompilationUnitTree tree = parser.parse("test", script, null);

    if (tree != null) {
        tree.accept(new SimpleTreeVisitorES6<Void, Void>() {
            @Override
            public Void visitVariable(VariableTree tree, Void v) {
                final Kind kind = tree.getKind();
                System.out.println("Found a variable: " + kind);
                System.out.println(" name: " + kind.toString());
                IdentifierTree binding = (IdentifierTree) tree.getBinding();
                System.out.println(" kind: " + binding.getKind().name());
                System.out.println(" name: " + binding.getName());
                System.out.println(" val: " + kind.name());

                return null;
            }
         }, null);
    }  

1 个答案:

答案 0 :(得分:0)

Nashorn开发人员之一。您想要做的是在源代码上计算所谓的def-use关系(很可能是它们的传递闭包,但是我离题了)。这是一个很好理解的编译器理论概念。好消息是CompilationUnitTree和朋友应该给您足够的信息,以实现用于计算该信息的算法。坏消息是,恐怕您将不得不袖手旁观并自行实施。您基本上必须收集这些信息,在控制流连接点(循环的后边缘和出口,if语句的末端)产生合并,但是还必须处理更多奇特的东西,例如switch / case及其后缀语义和还尝试/捕获/最终,这是最不有趣的,因为基本上控制权可以从try块中的任何地方转移到catch块。)您的算法还必须反复评估循环主体,直到收集到的静态信息达到定点。

FWIW,在编写Nashorn时,我不得不使用Nashorn的内部解析器API来实现这些事情几次(这是不同的,但与公共的相似)。如果您需要一些启发,可以在我几年前写的JavaScript函数中研究source code for Nashorn static type analyzer for inferring types of local variables。如果没有其他问题,它将为您提供一个思路,如何走一棵AST树并跟踪控制流边缘以及边缘处的部分计算的静态分析数据。

我希望可以有一种更简单的方法来完成…FWIW,这是一种广义的静态分析器,可以帮助您保持流控制的布尔值。祝你好运。