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

How do I track variable dependencies in Nashorn?

我想使用 Nashorn 引擎作为通用计算引擎。它功能强大,速度快,内置功能丰富,添加新功能非常容易,使用 or static methods。更好的是,它还提供循环依赖检查、语法检查等增值功能

但是我需要在依赖项发生变化时自动更新 "output" 变量。

一般的想法是,在 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 服务 class 知道如何使用 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) returns [datum4] 和 whatDependsOn(datum1) returns [datum3, datum4] ?

或者反函数 getReferencedVariables 这样 getReferencedVariables(datum3) returns [datum1] 和 getReferencedVariables(datum4) returns [datum2, datum3] (我可以递归地查询 getReferencedVariables 直到找到所有引用的变量)。

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

我知道 但我不知道如何使用 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);
    }  

这里是一位 Nashorn 开发者。您正在尝试做的是在源代码上计算所谓的 def-use 关系(好吧,更可能是它们的传递闭包,但我离题了)。这是一个很好理解的编译器理论概念。好消息是 CompilationUnitTree 和朋友应该给你足够的信息来实现计算这些信息的算法。坏消息是,恐怕您将不得不卷起袖子自己实施。您基本上必须收集这些信息,在控制流连接点(循环的后缘和出口,if 语句的结尾)产生合并,但您还必须处理更多奇特的东西,例如 switch/case 及其 fallthrough语义以及 try/catch/finally,这是其中最不有趣的,因为基本上控制可以从 try 块中的任何地方转移到 catch 块。)您的算法还必须重复评估循环体,直到您收集静态信息到达固定点。

FWIW,在编写 Nashorn 时,我不得不使用 Nashorn 的内部解析器 API(与 public 不同但相似)实现了几次这类事情。如果你想要一些灵感,你可以查看我几年前写的 JavaScript 函数中的 source code for Nashorn static type analyzer for inferring types of local variables 。如果不出意外,它将让您了解如何遍历 AST 树并跟踪控制流边缘和边缘处的部分计算静态分析数据。

我希望有一种更简单的方法来做到这一点……FWIW,一个通用的静态分析器可以帮助您记录流量控制。祝你好运。