Aviator这么丝滑,怎么实现的呢?

2023-10-26

大家好,我是老三,在上期 里我们介绍了轻量级规则引擎AviatorScript的基本用法和一些使用案例,这期我们来研究一下,这么丝滑的规则脚本是怎么实现的。

概览

我们先来回顾一个简单的例子:

 @Test
  public void test(){
    //表达式脚本
    String expression = "if(a > 1) { return b+2; } else { return c; }";
    //一:编译
    Expression compiledExpression=AviatorEvaluator.getInstance().compile(expression);
    //上下文
    Map<String, Object> env = new HashMap<>();
    env.put("a", 2);
    env.put("b", 3);
    env.put("c", 4);
    //二、执行
    Object result =compiledExpression.execute(env);
    System.out.println(result);
  }

这是一个很简单的is-else·脚本,脚本里做了一个条件判断,分别返回不同的值。

要执行这个脚本,主要分为两步,一是编译,二是执行,AviatorScript的具体实现,就藏在了这两大步里,我们一起去挖掘Aviator实现的秘密吧。

原理概览

编译compile()

接着上面这个例子,我们进去看一下compile()的关键代码:

   public Expression compile(final String expression) {
        return compile(expression, this.cachedExpressionByDefault);
    }

一层套一层的compile

经过一层套一层的compile(),我们终于看到一个代码多一些的compile()方法。

compile()

分析一下这段代码:

  1. 根据传入的cached参数判断是否启用缓存。如果启用缓存,则会执行以下步骤:

    a. 如果使用了LRU(最近最少使用)缓存策略,那么首先会尝试从LRU缓存中获取已经存在的FutureTask对象,该对象用于表示正在编译的任务。如果存在,就返回已编译的表达式;如果不存在,则创建一个新的编译任务(newCompileTask),并将其放入LRU缓存中。

    b. 如果没有使用LRU缓存(this.expressionLRUCache为null),那么会尝试从普通的缓存(this.expressionCache)中获取已经存在的FutureTask对象。如果存在,就返回已编译的表达式;如果不存在,则创建一个新的编译任务,并将其放入缓存中。

  2. 如果在缓存中找到了对应的编译任务(existedTask),则执行该任务(existedTask.run())并返回已编译的表达式。

  3. 如果不启用缓存(cached为false),则调用innerCompile方法进行实际的编译操作,并返回编译后的表达式。

到了这里,我们终于抓住了执行实际编译的方法innerCompile(),继续跟着源码前进。

innerCompile()

innerCompile()这个方法就是实际用来来执行编译的方法:

innerCompile流程

    /**
     * 执行真正的编译操作:将给定的表达式进行词法分析、语法分析,并生成一个完整的表达式对象
     * @param expression  需要编译的表达式
     * @param sourceFile  源文件路径
     * @param cached 是否启用缓存
     * @return 表达式对象
     */
    private Expression innerCompile(final String expression, final String sourceFile,
                                    final boolean cached) {
        //1.创建表达式词法分析器:通过传入当前对象和表达式,创建 ExpressionLexer 对象。ExpressionLexer 用于对表达式进行词法分析,如将脚本解析为变量、数字、字符串、注释等,并构造Token流进行后续处理。
        ExpressionLexer lexer = new ExpressionLexer(this, expression);
        //2.创建字节码生成器:调用 newCodeGenerator 方法创建 CodeGenerator 对象。CodeGenerator 用于生成自定义的字节码
        CodeGenerator codeGenerator = newCodeGenerator(sourceFile, cached);
        //3.创建表达式解析器:通过传入当前对象、Lexer 和 CodeGenerator,创建 ExpressionParser 对象。ExpressionParser 用于将词法分析得到的标记组装成一个完整的表达式。
        ExpressionParser parser = new ExpressionParser(this, lexer, codeGenerator);
        //4.解析表达式:调用 parser 的 parse 方法,将词法分析得到的标记解析为一个表达式对象(exp)。
        Expression exp = parser.parse();
        //5.设置表达式文本(可选):根据选项 Options.TRACE_EVAL 的值来判断是否需要设置表达式对象的文本。如果 Options.TRACE_EVAL 为 true,则将 expression 设置在 exp 对象中,用于跟踪和调试目的。
        if (getOptionValue(Options.TRACE_EVAL).bool) {
            ((BaseExpression) exp).setExpression(expression);
        }
        //6.返回表达式对象:返回解析后的表达式对象 exp。
        return exp;
    }

在这段代码里,创建了几个关键的角色:

三个关键角色

  • ExpressionLexer:词法分析器,它用于将AviatorScript脚本解析成变量、数字、字符串、注释等不同的词法单元(Token),并构造一个Token流以供后续处理。
  • CodeGenerator:字节码生成器,它用于动态生成自定义的字节码。在AviatorScript中,CodeGenerator负责将ExpressionLexer生成的Token流编译成表达式对象的字节码。
  • ExpressionParser:表达式解析器,它用于将AviatorScript脚本编译为表达式对象(通常是ClassExpression)。ExpressionParser支持解析多种AviatorScript脚本,并且在解析过程中会利用CodeGenerator将ExpressionLexer构建的Token流编译成适当的表达式对象(比如ClassExpression)。

ExpressionLexer词法分析器

ExpressionLexer负责对AviatorScript脚本进行词法分析,将其解析为Token流。

  public Token<?> scan() {
    return this.scan(true);
  }

这个类里代码不多,最重要的方法就是scan,用于扫描输入的字符串并将其转换为不同类型的词法单元(Token),它会在创建ExpressionParser的时候被调用。

具体来说,它按照一定的规则逐个字符地读取输入,并根据字符的特征确定相应的词法单元类型。

scan方法

CodeGenerator字节码生成器

CodeGenerator用于动态生成自定义的字节码。

我们先来看下它的继承体系:

CodeGenerator

  • CodeGenerator是一个顶层接口,定义了字节码生成的方法和规范。
  • ASMCodeGenerator是默认的实现类,它基于ASM框架进行具体的字节码生成。它实现了CodeGenerator接口中声明的操作,将代码转化为ASM指令并生成对应的字节码。
  • OptimizeCodeGenerator是一个优化过的字节码生成器,默认情况下也使用的是ASMCodeGenerator作为底层实现。OptimizeCodeGenerator可以提高提高执行效率,在生成字节码之前,它可以执行一些预处理的计算逻辑,然后再将结果交给ASMCodeGenerator来生成字节码。通过这种优化,可以减少运行时的计算量,提高脚本执行的效率。
  • LambdaGenerator是一个用于生成Lambda表达式代码的代码生成器。它接受一个父级代码生成器和其他参数作为输入,并通过调用内部的codeGenerator来生成Lambda表达式的代码。

ASM拾遗

这里再简单学习一下字节码技术,因为ASM在Aviator里扮演了非常重要的作用,是Aviator依赖的核心类库。

ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

看一个简单的例子,Hello World都写过吧:

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

Java代码在执行前,需要先编译,那么要生成和这个类编译之后相同的Java类文件(.class文件),用ASM怎么实现呢?

稍微有点繁琐:

public class HelloWorldGenerator {

    public static void main(String[] args) throws IOException {
        // 1.创建一个 ClassWriter 实例,以生成一个新的类
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // 2.定义类的基本信息:访问修饰符、类名、父类、接口
        cw.visit(Opcodes.V11, Opcodes.ACC_PUBLIC, "Hello", null, "java/lang/Object", null);

        // 3.定义主方法
        MethodVisitor mainMethod = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main",
                "([Ljava/lang/String;)V", null, null);
        mainMethod.visitCode();
        mainMethod.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        mainMethod.visitLdcInsn("Hello, World!");
        mainMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println",
                "(Ljava/lang/String;)V", false);
        mainMethod.visitInsn(Opcodes.RETURN);
        mainMethod.visitMaxs(2, 1);
        mainMethod.visitEnd();

        //4.类结束
        cw.visitEnd();

        //5.将字节码写入文件
        byte[] byteCode = cw.toByteArray();
        FileOutputStream fos = new FileOutputStream("Hello.class");
        fos.write(byteCode);
        fos.close();
    }
}

在Idea里可以直接反编译生成的字节码文件:

Hello.class

ASM框架,主要由这几个核心组件组成:

核心组件

  1. ClassReader(类读取器):ClassReader用于读取已编译的Java类文件的字节码。它将字节码解析为可以访问和操作的结构化表示形式。
  2. ClassWriter(类写入器):ClassWriter用于生成新的Java类文件的字节码。它提供了一组API来创建类、方法、字段和指令等,并将它们转换为字节码形式。
  3. ClassVisitor(类访问器):ClassVisitor是一个接口,用于访问和修改正在被读取或写入的类。通过实现ClassVisitor接口,开发人员可以自定义对类的访问方式,并在访问过程中进行相应的操作。
  4. MethodVisitor(方法访问器):MethodVisitor是ClassVisitor的子接口,用于访问和修改正在被读取或写入的方法。通过实现MethodVisitor接口,开发人员可以自定义对方法的访问方式,并在访问过程中进行相应的操作。
  5. FieldVisitor(字段访问器):FieldVisitor是ClassVisitor的子接口,用于访问和修改正在被读取或写入的字段。通过实现FieldVisitor接口,开发人员可以自定义对字段的访问方式,并在访问过程中进行相应的操作。
  6. AnnotationVisitor(注解访问器):AnnotationVisitor是ClassVisitor的子接口,用于访问和修改正在被读取或写入的注解。通过实现AnnotationVisitor接口,开发人员可以自定义对注解的访问方式,并在访问过程中进行相应的操作。

……

简单了解一下ASM技术之后,接下来我们看看核心的代码生成器ASMCodeGenerator。

ASMCodeGenerator

ASMCodeGenerator在Aviator中的作用是将用户提供的表达式转换为可执行的Java字节码,并且能够动态生成类和方法来存储和执行这些字节码。

包括其它几个CodeGenerator最后调用的也是ASMCodeGenerator

ASMCodeGenerator

ASMCodeGenerator是对ASM的一个封装,加入了一些定制化的逻辑判断,其实生成代码的主要流程和我们前面HelloWorld的例子是类似的。

  1. 创建 ClassWriter 对象,用于生成类文件的字节码。

    ASMCodeGenerator的构造方法:

      public ASMCodeGenerator(final AviatorEvaluatorInstance instance, final String sourceFile,
          final AviatorClassLoader classLoader, final OutputStream traceOut) {
        super(instance, sourceFile, classLoader);
        //创建内部类型
        this.className = "Script_" + System.currentTimeMillis() + "_" + CLASS_COUNTER.getAndIncrement();
        //创建ClassWriter, 自动填充栈帧信息
        this.classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        //访问类
        visitClass();
      }
    
  2. 定义类名、父类、接口等基本信息,并开始访问类

    visitClass()方法:

      /**
       * 访问类,生成类的相关信息
       */
      private void visitClass() {
        // 设置类的基本信息
        this.classWriter.visit(this.instance.getBytecodeVersion(), ACC_PUBLIC + ACC_SUPER,
                this.className, null, "com/googlecode/aviator/ClassExpression", null);
        // 设置类的源文件名
        this.classWriter.visitSource(this.sourceFile == null ? this.className : this.sourceFile, null);
      }
    
  3. 设置类的字段和方法,生成对应的字段和方法的字节码。

    • 比如在启动代码生成过程的 start()里生成了一个默认的构造函数,并且开始生成方法代码。
      /**
       * 启动代码生成过程
       */
      @Override
      public void start() {
        //生成构造函数的代码
        makeConstructor();
        //用于开始生成方法的代码
        startVisitMethodCode();
      }
    
    • makeConstructor()方法:
     private void makeConstructor() {
    
        // 创建公共构造函数
        this.mv = this.classWriter.visitMethod(ACC_PUBLIC, CONSTRUCTOR_METHOD_NAME,
                "(Lcom/googlecode/aviator/AviatorEvaluatorInstance;Ljava/util/List;Lcom/googlecode/aviator/lexer/SymbolTable;)V",
                null, null);
        this.mv.visitCode(); // 开始访问构造函数的代码
    
        // 调用父类的构造函数
        this.mv.visitVarInsn(ALOAD, 0);
        this.mv.visitVarInsn(ALOAD, 1);
        this.mv.visitVarInsn(ALOAD, 2);
        this.mv.visitVarInsn(ALOAD, 3);
        this.mv.visitMethodInsn(INVOKESPECIAL, "com/googlecode/aviator/ClassExpression",
                CONSTRUCTOR_METHOD_NAME,
                "(Lcom/googlecode/aviator/AviatorEvaluatorInstance;Ljava/util/List;Lcom/googlecode/aviator/lexer/SymbolTable;)V");
    
        // 设置内部变量的值
        if (!this.innerVars.isEmpty()) {
          for (Map.Entry<String, String> entry : this.innerVars.entrySet()) {
            String outterName = entry.getKey();
            String innerName = entry.getValue();
            this.mv.visitVarInsn(ALOAD, 0);
            this.mv.visitTypeInsn(NEW, JAVA_TYPE_OWNER);
            this.mv.visitInsn(DUP);
            this.mv.visitLdcInsn(outterName);
            this.mv.visitVarInsn(ALOAD, 3);
            this.mv.visitMethodInsn(INVOKESPECIAL, JAVA_TYPE_OWNER, CONSTRUCTOR_METHOD_NAME,
                    "(Ljava/lang/String;Lcom/googlecode/aviator/lexer/SymbolTable;)V");
            this.mv.visitFieldInsn(PUTFIELD, this.className, innerName,
                    "Lcom/googlecode/aviator/runtime/type/AviatorJavaType;");
          }
        }
    
        // 设置内部方法的值
        if (!this.innerMethodMap.isEmpty()) {
          for (Map.Entry<String, String> entry : this.innerMethodMap.entrySet()) {
            String outterName = entry.getKey();
            String innerName = entry.getValue();
            this.mv.visitVarInsn(ALOAD, 0);
            this.mv.visitVarInsn(ALOAD, 1);
            this.mv.visitLdcInsn(outterName);
            this.mv.visitVarInsn(ALOAD, 3);
            this.mv.visitMethodInsn(INVOKEVIRTUAL, "com/googlecode/aviator/AviatorEvaluatorInstance",
                    "getFunction",
                    "(Ljava/lang/String;Lcom/googlecode/aviator/lexer/SymbolTable;)Lcom/googlecode/aviator/runtime/type/AviatorFunction;");
            this.mv.visitFieldInsn(PUTFIELD, this.className, innerName,
                    "Lcom/googlecode/aviator/runtime/type/AviatorFunction;");
          }
        }
    
        // 设置常量池中的值
        if (!this.constantPool.isEmpty()) {
          for (Map.Entry<Token<?>, String> entry : this.constantPool.entrySet()) {
            Token<?> token = entry.getKey();
            String fieldName = entry.getValue();
            this.mv.visitVarInsn(ALOAD, 0);
            onConstant0(token, true);
            this.popOperand();
            this.mv.visitFieldInsn(PUTFIELD, this.className, fieldName, OBJECT_DESC);
          }
        }
    
        this.mv.visitInsn(RETURN); // 返回
        this.mv.visitMaxs(4, 1); // 最大栈深度和局部变量数
        this.mv.visitEnd(); // 结束访问构造函数
      }
    
  4. 在方法的字节码生成过程中,根据代码逻辑和类型信息,生成相应的字节码指令和操作数。

    • 比如这个方法onTernaryLeft0,表示处理三元表达式的左侧逻辑:
    /**
     * 处理三元表达式的左侧逻辑
     * @param lookhead 当前处理的标记
     */
    @Override
    public void onTernaryLeft(final Token<?> lookhead) {
        // 跳转到 peekLabel1() 标签处执行
        this.mv.visitJumpInsn(GOTO, peekLabel1());
        // 访问 popLabel0() 标签
        visitLabel(popLabel0());
        // 访问当前代码行号
        visitLineNumber(lookhead);
        // 弹出一个布尔值
        this.popOperand();
    }
    
    

    在ASMCodeGenerator的方法里,只要是on开头的,基本都是根据一定的逻辑去生成对应的代码。

  5. 最后,结束访问类,输出字节码。

    • end()方法里结束方法体和结束类的访问
      /**
       * 结束方法体,对方法代码和类进行结束访问
       */
      private void end(final boolean unboxObject) {
        // 结束访问方法的代码
        endVisitMethodCode(unboxObject);
        // 结束访问类
        endVisitClass();
      }
    

这里我们就对ASMCodeGenerator有了一个大概的了解,ASMCodeGenerator在Aviator扮演的就是默默无闻的搬砖工,把底层的事情都干了。

ExpressionParser表达式解析器

ExpressionParser将Token流编译为表达式对象,它充当一个协调者的角色,战斗在编译工作的一线。

我们先来看下构造方法:

 /**
   * ExpressionParser的构造方法
   */
  public ExpressionParser(final AviatorEvaluatorInstance instance, final ExpressionLexer lexer,
      final CodeGenerator codeGenerator) {
    super();
    //创建了一个 ScopeInfo 对象,并将其赋值给 scope 属性。ScopeInfo 对象用于跟踪变量和函数的作用域信息。
    this.scope = new ScopeInfo(0, 0, 0, 0, false, new ArrayDeque<DepthState>());
    //将传入的 AviatorEvaluatorInstance 实例赋值给 instance 属性。
    this.instance = instance;
    //从 instance 中获取选项 CAPTURE_FUNCTION_ARGS 的值,并将其设置为解析器的 captureFuncArgs 属性。
    this.captureFuncArgs = instance.getOptionValue(Options.CAPTURE_FUNCTION_ARGS).bool;
    //将传入的 ExpressionLexer 实例赋值给 lexer 属性
    this.lexer = lexer;
    //⚡⚡⚡通过调用 lexer 的 scan() 方法,获取下一个 Token,并将其赋值给 lookhead 属性。lookhead 表示当前待处理的 Token。
    this.lookhead = this.lexer.scan();
    //如果 lookhead 不为空,则将 parsedTokens 的值加1。parsedTokens 表示已经解析的 Token 数量。
    if (this.lookhead != null) {
      this.parsedTokens++;
    }
    //从 instance 中获取选项 FEATURE_SET 的值,并将其设置为解析器的 featureSet 属性。featureSet 表示当前使用的特性集合。
    this.featureSet = this.instance.getOptionValue(Options.FEATURE_SET).featureSet;
    //如果 lookhead 为空,则报告语法错误,提示输入的脚本为空。
    if (this.lookhead == null) {
      reportSyntaxError("blank script");
    }
    //设置解析器的代码生成器为传入的 CodeGenerator 实例。
    setCodeGenerator(codeGenerator);
    //通过调用 getCodeGeneratorWithTimes() 方法获取代码生成器,并将解析器实例设置为其属性中的 parser。
    getCodeGeneratorWithTimes().setParser(this);
  }

在这段代码里我们可以看到前面说的调用词法分析器的部分:

//⚡⚡⚡通过调用 lexer 的 scan() 方法,获取下一个 Token,并将其赋值给 lookhead 属性。lookhead 表示当前待处理的 Token。
 this.lookhead = this.lexer.scan();

parse()

接下来我们看看直接干活的parse()方法,通过parse()方法完成对Token的解析。

 //4.解析表达式:调用 parser 的 parse 方法,将词法分析得到的标记解析为一个表达式对象(exp)。
  Expression exp = parser.parse();

比如if,for,let等脚本特性,解析逻辑都是依赖parse()方法完成的:

  public Expression parse(final boolean reportErrorIfNotEOF) {
    //调用statements()方法解析一系列语句,并将结果保存在statementType变量中。
    //StatementType表示语句的类型,可能是三元表达式(StatementType.Ternary)或其他类型的语句。
    StatementType statementType = statements();
    if (this.lookhead != null && reportErrorIfNotEOF) {
      if (statementType == StatementType.Ternary) {
        reportSyntaxError("unexpect token '" + currentTokenLexeme()
            + "', maybe forget to insert ';' to complete last expression ");
      } else {
        reportSyntaxError("unexpect token '" + currentTokenLexeme() + "'");
      }
    }
    //将代码生成器的结果作为返回值返回,即返回一个Expression对象
    return getCodeGeneratorWithTimes().getResult(true);
  }

这段代码,主要做了两件事情:

  • 调用statements()方法解析一系列语句,并将结果保存在statementType变量中。StatementType表示语句的类型,可能是三元表达式(StatementType.Ternary)或其他类型的语句。
  • 将代码生成器的结果作为返回值返回,即返回一个Expression对象

statements()

我们来看下statements(),大体上是循环解析一系列Token,返回最后一个Token解析的StatementType。

statements()

具体的解析在私有的statement()方法里里,源码扒出来看下:

 /**
     * 根据当前标记的类型选择相应的语句处理方法,并返回相应的语句类型。
     * 包括条件语句、循环语句、返回语句、异常处理语句、作用域语句、函数声明语句和三元表达式。
     * 根据不同的语句类型,调用相应的处理方法,并返回对应的语句类型。如果无法匹配任何语句类型,则返回StatementType.Empty表示空语句。
     * @return
     */
     private StatementType statement() {
        if (this.lookhead == Variable.IF) {
            // 确保 if 语句的功能已启用
            ensureFeatureEnabled(Feature.If);

            // 解析 if 语句,并返回是否成功解析了 return 语句
            if (ifStatement(false, false)) {
                return StatementType.Return;
            } else {
                return StatementType.Other;
            }
        } else if (this.lookhead == Variable.FOR) {
            // 确保 for 循环的功能已启用
            ensureFeatureEnabled(Feature.ForLoop);

            // 解析 for 循环语句
            forStatement();

            return StatementType.Other;
        } else if (this.lookhead == Variable.RETURN) {
            // 确保 return 语句的功能已启用
            ensureFeatureEnabled(Feature.Return);

            // 解析 return 语句
            returnStatement();

            return StatementType.Return;
        } else if (this.lookhead == Variable.BREAK) {
            // 解析 break 语句
            breakStatement();

            return StatementType.Return;
        } else if (this.lookhead == Variable.CONTINUE) {
            // 解析 continue 语句
            continueStatement();

            return StatementType.Return;
        } else if (this.lookhead == Variable.LET) {
            // 确保 let 语句的功能已启用
            ensureFeatureEnabled(Feature.Let);

            // 解析 let 语句
            letStatement();

            return StatementType.Other;
        } else if (this.lookhead == Variable.WHILE) {
            // 确保 while 循环的功能已启用
            ensureFeatureEnabled(Feature.WhileLoop);

            // 解析 while 循环语句
            whileStatement();

            return StatementType.Other;
        } else if (this.lookhead == Variable.FN) {
            // 确保 fn 语句的功能已启用
            ensureFeatureEnabled(Feature.Fn);

            // 解析 fn 语句
            fnStatement();

            return StatementType.Other;
        } else if (this.lookhead == Variable.TRY) {
            // 确保异常处理的功能已启用
            ensureFeatureEnabled(Feature.ExceptionHandle);

            // 解析 try 语句
            tryStatement();

            return StatementType.Other;
        } else if (this.lookhead == Variable.THROW) {
            // 确保异常处理的功能已启用
            ensureFeatureEnabled(Feature.ExceptionHandle);

            // 解析 throw 语句
            throwStatement();

            return StatementType.Other;
        } else if (expectChar('{')) {
            // 确保词法作用域的功能已启用
            ensureFeatureEnabled(Feature.LexicalScope);

            // 解析作用域语句,并返回是否成功解析了 return 语句
            if (scopeStatement()) {
                return StatementType.Return;
            } else {
                return StatementType.Other;
            }
        } else if (this.lookhead == Variable.USE) {
            // 确保 use 语句的功能已启用
            ensureFeatureEnabled(Feature.Use);

            // 解析 use 语句
            useStatement();

            return StatementType.Other;
        } else {
            if (ternary()) {
                return StatementType.Ternary;
            } else {
                return StatementType.Empty;
            }
        }
    }

看到这里,老三不由感慨,原来再牛的规则引擎,底层也是一堆的if-sle

这个方法根据当前的 Token 类型来解析不同类型的语句,并返回每个语句的类型。

ifStatement()

我们来抓住一个典型,比如代码里最常用的if,看看Aviator是怎么处理的,试着管中窥豹,分析Aviator的设计。

    /**
     * *用于处理条件语句(if语句):
     * 接受两个布尔值参数isWhile和isElsif,用于确定当前条件语句是否是while语句或者是elsif语句。
     * <pre>
     *  if(test) {
     *     ...if-body...
     *  }else {
     *     ...else-body...
     *  }
     *  ...statements...
     * </pre>
     * <p>
     * ===>
     *
     * <pre>
     *  __if_callcc(test ? (lambda() -> ...if-body... end)() :  (lambda() -> ...else-body... end)(),
     *   lambda()- >
     *       ...statements...
     *  end);
     * </pre>
     */
    private boolean ifStatement(final boolean isWhile, final boolean isElsif) {
        // 移动到下一个 Token
        if (!isWhile) {
            move(true);
        }

        // 标记 if 语句的主体和 else 语句的返回情况
        boolean ifBodyHasReturn = false;
        boolean elseBodyHasReturn = false;

        // 设置新的词法作用域
        boolean newLexicalScope = this.scope.newLexicalScope;
        this.scope.newLexicalScope = true;

        // 准备调用 __if_callcc(result, statements)
        getCodeGeneratorWithTimes().onMethodName(Constants.IfReturnFn);

        // 解析 if 语句的条件部分
        {
            //对三元表达式(ternary),调用相应的代码生成方法进行处理。
            if (!ternary()) {
                reportSyntaxError("missing test statement for if");
            }

            getCodeGeneratorWithTimes().onTernaryBoolean(this.lookhead);

            // 解析 if 语句的主体部分
            if (expectChar('{')) {
                move(true);
                //调用 this.scope.enterBrace() 进入一个新的花括号作用域
                this.scope.enterBrace();
                //调用代码生成器的 onLambdaDefineStart() 方法,表示开始定义一个 lambda 函数
                getCodeGeneratorWithTimes().onLambdaDefineStart(
                        getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope));
                //调用代码生成器的 onLambdaBodyStart() 方法,表示 lambda 函数的主体部分开始
                getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookhead);
                //调用 statements() 方法解析 lambda 函数的主体部分,判断主体部分的类型是否为 Return,并将结果赋值给 ifBodyHasReturn 变量
                ifBodyHasReturn = statements() == StatementType.Return;
                //调用代码生成器的 onLambdaBodyEnd() 方法,表示 lambda 函数的主体部分结束
                getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookhead);
                //调用代码生成器的 onMethodName() 方法,使用匿名方法名
                getCodeGeneratorWithTimes().onMethodName(anonymousMethodName());
                // 调用代码生成器的 onMethodInvoke() 方法,生成方法调用
                getCodeGeneratorWithTimes().onMethodInvoke(this.lookhead);
                //调用代码生成器的 onTernaryLeft() 方法,表示条件表达式的左侧部分
                getCodeGeneratorWithTimes().onTernaryLeft(this.lookhead);

            } else {
                reportSyntaxError("expect '{' for if statement");
            }

            // 验证 if 语句的主体部分是否有正确的右大括号结束
            if (!expectChar('}')) {
                reportSyntaxError("missing '}' to close if body");
            }

            this.scope.leaveBrace();
            move(true);

            // 解析 else 语句
            elseBodyHasReturn = elseStatement(isWhile, ifBodyHasReturn);
            getCodeGeneratorWithTimes().onMethodParameter(this.lookhead);
        }

        // 处理 if 语句后面的陈述部分
        {
            if (isWhile || isElsif) {
                // 直接加载 ReducerEmptyVal
                getCodeGenerator().onConstant(Constants.ReducerEmptyVal);
            } else {
                if (expectChar(';')) {
                    // 陈述部分已结束,加载 ReducerEmptyVal
                    getCodeGenerator().onConstant(Constants.ReducerEmptyVal);
                } else {
                    // 创建一个包含 if 语句后陈述部分的 lambda 函数
                    getCodeGeneratorWithTimes().onLambdaDefineStart(
                            getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope)
                                    .withMeta(Constants.INHERIT_ENV_META, true));
                    getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookhead);
                    if (statements() == StatementType.Empty) {
                        // 陈述部分为空,加载 ReducerEmptyVal
                        getCodeGenerator().onConstant(Constants.ReducerEmptyVal);
                    } else {
                        if (ifBodyHasReturn && elseBodyHasReturn && !isElsif) {
                            reportSyntaxError("unreachable code");
                        }
                    }
                    getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookhead);
                }
            }
            getCodeGenerator().onMethodParameter(this.lookhead);
            // 调用 __if_callcc(result, statements)
            getCodeGenerator().onMethodInvoke(this.lookhead);
            this.scope.newLexicalScope = newLexicalScope;
        }
        //返回if主体和else主体是否有返回语句的布尔值
        return ifBodyHasReturn && elseBodyHasReturn;
    }

在这段代码开头,作者给出了注释,这个方法会对if脚本进行转换,抽象成一个lambda表达式:

转换

把if语句转换为使用 __if_callcc 函数的形式。转换后的代码使用了三元表达式(ternary)来选择执行 ...if-body... 或者 ...else-body...,并在转换后的代码中使用lambda函数来表示if和else的主体部分。

这段代码比较长,主要的解析过程可以分为以下几个部分:

整体语句划分

  1. 解析三元表达式:通过ternary()方法完成对三元表达式的解析和处理(解析结果放入OptimizeCodeGenerator的Token流中,后续统一生成字节码)

  2. 解析if的主体部分:将if的方法体(ifBody)抽象为一个lambda表达式,并委托给lambdaGenerator进行解析;

  3. 解析else的主体部分:调用了elseStatement(),实际的解析过程和ifBody的解析过程类似,也是委托给新构建的lambdaGenerator进行解析;

elseStatement()

  1. 最后对对if语句的陈述部分解析:也可以理解成对整个代码块进行处理,抽象成一个lambda表达式,也是委托给lambdaGenerator进行解析;

接下来我们再研究一下lambda()的秘密。

LambdaGenerator

我们从这段代码开始看起:

lam

可以大体上把生成lambda脚本解析生成分为三步:

  1. 解析前:
    • onLambdaDefineStart()创建并配置一个 LambdaGenerator 实例,用于处理 lambda 函数的生成和编译过程。它还负责设置 lambda 函数的词法作用域,并确保每个 lambda 函数只能在一个作用域中定义。

onLambdaDefineStart()

  • onLambdaBodyStart(this.lookhead)调用代码生成器的onLambdaBodyStart() 方法,准备开始处理lambda 函数的主体部分。

    它主要的作用是在处理 lambda 函数的主体部分时,将当前的代码生成器切换为 lambda 生成器。这样可以确保 lambda 函数的代码生成过程是独立的,并且符合 lambda 函数的语义和要求。

onLambdaBodyStart

  1. 解析中:

    • 具体的解析就不用多说了,因为还是接着调用statements(),绕来绕去又绕回去了。
  2. 解析后

    • onLambdaBodyEnd()方法里,会调用getLmabdaBootstrap构造LambdaFunctionBootstrap实例对象,并且缓存起来。

    在这里插入图片描述

    • 进一步展开getLmabdaBootstrap(),这里把Lambda 函数的信息打包成一个 LambdaFunctionBootstrap 对象,并返回该对象。LambdaFunctionBootstrap 是一个用于表示 Lambda 函数的引导类,它包含了 Lambda 函数的类名、表达式、参数列表和是否继承环境等信息。

    getLmabdaBootstrap()

这里也可以看到调用了getResult()方法,这个方法里,会调用代码生成器,去生成字节码。

getResult()

先回顾一下parse()的代码最后一句:

        //将代码生成器的结果作为返回值返回,即返回一个Expression对象
        return getCodeGeneratorWithTimes().getResult(true);

Aviator脚本解析完成之后,解析结果Token流会存放到OptimizeCodeGenerator的成员变量List<Token<?>> tokenList中,getResult方法就是根据tokenList生成字节码的过程。

OptimizeCodeGeneratordgetResult()方法具体实现如下:

 /**
     * 根据给定的tokenList列表生成相应的表达式对象。
     * 它根据不同类型的token,对变量、方法和常量进行处理,并根据情况返回对应的表达式对象。
     * 其中包括执行字面量表达式、创建变量和常量的映射集合、调用 ASM(Java字节码框架)生成字节码等操作。
     * 最后返回生成的表达式对象
     */
    @Override
    public Expression getResult(final boolean unboxObject) {
        // 执行字面量表达式
        while (execute() > 0) {
            ;
        }

        // 创建变量、方法和常量的映射集合
        Map<String, VariableMeta/* metadata */> variables = new LinkedHashMap<String, VariableMeta>();
        Map<String, Integer/* counter */> methods = new HashMap<String, Integer>();
        Set<Token<?>> constants = new HashSet<>();

        // 遍历tokenList列表
        for (Token<?> token : this.tokenList) {
            // 如果token是常量,则将其添加到constants集合中
            if (ExpressionParser.isConstant(token, this.instance)) {
                constants.add(token);
            }
            // 根据不同的token类型进行不同的处理
            switch (token.getType()) {
                case Variable:
                    if (SymbolTable.isReservedKeyword((Variable) token)) {
                        continue;
                    }

                    String varName = token.getLexeme();
                    VariableMeta meta = variables.get(varName);
                    // 如果变量在variables集合中不存在,则创建新的VariableMeta对象,并将其添加到variables集合中
                    if (meta == null) {
                        meta = new VariableMeta((CompileTypes) token.getMeta(Constants.TYPE_META), varName,
                                token.getMeta(Constants.INIT_META, false), token.getStartIndex());
                        variables.put(varName, meta);
                    } else {
                        meta.add(token);
                    }
                    break;
                case Delegate:
                    DelegateToken delegateToken = (DelegateToken) token;
                    // 如果是委托类型的token,则根据不同的委托类型进行不同的处理
                    if (delegateToken.getDelegateTokenType() == DelegateTokenType.Method_Name) {
                        Token<?> realToken = delegateToken.getToken();
                        if (realToken == null) {
                            continue;
                        }
                        if (realToken.getType() == TokenType.Variable) {
                            String methodName = token.getLexeme();
                            // 统计方法出现的次数,并将其添加到methods集合中
                            if (!methods.containsKey(methodName)) {
                                methods.put(methodName, 1);
                            } else {
                                methods.put(methodName, methods.get(methodName) + 1);
                            }
                        }
                    } else if (delegateToken.getDelegateTokenType() == DelegateTokenType.Array) {
                        Token<?> realToken = delegateToken.getToken();
                        if (realToken.getType() == TokenType.Variable) {
                            varName = token.getLexeme();
                            VariableMeta varMeta = variables.get(varName);
                            // 如果变量在variables集合中不存在,则创建新的VariableMeta对象,并将其添加到variables集合中
                            if (varMeta == null) {
                                varMeta =
                                        new VariableMeta((CompileTypes) realToken.getMeta(Constants.TYPE_META), varName,
                                                realToken.getMeta(Constants.INIT_META, false), realToken.getStartIndex());
                                variables.put(varName, varMeta);
                            } else {
                                varMeta.add(realToken);
                            }
                        }
                    }
                    break;
            }
        }

        Expression exp = null;

        // 如果tokenList的大小小于等于1,则根据情况返回对应的LiteralExpression对象
        if (this.tokenList.size() <= 1) {
            if (this.tokenList.isEmpty()) {
                exp = new LiteralExpression(this.instance, null, new ArrayList<>(variables.values()));
            } else {
                final Token<?> lastToken = this.tokenList.get(0);
                if (ExpressionParser.isLiteralToken(lastToken, this.instance)) {
                    exp = new LiteralExpression(this.instance,
                            getAviatorObjectFromToken(lastToken).getValue(getCompileEnv()),
                            new ArrayList<>(variables.values()));
                }
            }
        }

        if (exp == null) {
            // 调用ASM生成字节码
            callASM(variables, methods, constants);
            // 从ASM获取结果
            exp = this.codeGen.getResult(unboxObject);
        }

        // 对于BaseExpression对象,设置编译环境和源文件
        if (exp instanceof BaseExpression) {
            ((BaseExpression) exp).setCompileEnv(getCompileEnv());
            ((BaseExpression) exp).setSourceFile(this.sourceFile);
        }
        return exp;
    }

这里主要包含以下几部分:

  1. 可以前置执行的逻辑提前执行,比如文本表达式(1+2)等,先行计算出执行结果,优化执行效率

  2. 初始化常量集、变量集、aviator函数实例集合,为后续ASM生成类成员变量和类构造函数使用

  3. 调用callASM方法生成字节码,根据不同的Token类型进行不同的asm操作

callASM()方法

  1. 调用ASMCodeGeneratorgetResult()方法,结束代码生成过程,根据生成的字节码构造Expression子类实例(ClassExpression
  /*
   * (non-Javadoc)
   *
   * @see com.googlecode.aviator.code.CodeGenerator#getResult()
   * 生成并返回一个表达式(Expression)对象
   */
  @Override
  public Expression getResult(final boolean unboxObject) {
    // 调用 end(unboxObject) 方法结束代码生成过程,并获取生成的字节码数据 byte[] bytes
    end(unboxObject);

    // 使用 ClassDefiner.defineClass 方法动态定义一个类,并使用生成的字节码数据作为类的定义内容。
    // 这里定义的类是继承自 Expression 的类。返回的是一个 Class<?> 类型的对象,表示定义的类
    byte[] bytes = this.classWriter.toByteArray();
    // 									
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Aviator这么丝滑,怎么实现的呢? 的相关文章

随机推荐

  • Hadoop Core、HBase 、ZooKeeper

    adoop HBase ZooKeeper三者关系与安装配置 复制链接 qqjue 论坛徽章 18 电梯直达 1
  • 互联网的组成——边缘部分+核心部分 (互联网报文交换方式:分组转发技术)

    首先 复习的第一本书是 计算机网络 作为计算机专业的学生 计算机网络是必须学好的一门功课 互联网的组成 边缘部分 核心部分 从互联网的工作方式上看 可以划分为两大块 1 边缘部分 由所有连接在互联网上的主机组成 这部分是用户直接使用的 用来
  • 特征工程系列:自动化特征构造

    特征工程系列 自动化特征构造 原创 JunLiang 木东居士 今天 0x00 前言 数据和特征决定了机器学习的上限 而模型和算法只是逼近这个上限而已 由此可见 特征工程在机器学习中占有相当重要的地位 在实际应用当中 可以说特征工程是机器学
  • Python 中的默认值是什么?

    Python 语言具有表示函数参数的语法和默认值的不同方式 默认值指示如果在函数调用期间未给出参数值 则函数参数将采用该值 默认值是使用表单关键字名称 值的赋值 运算符分配的 例 creating a function by giving
  • JavaWeb——Servlet(入门必备,web请求与响应的底层)

    这里写自定义目录标题 1 servlet介绍 1 1 什么是servlet 1 2 手动实现servlet程序的步骤 2 servlet的生命周期 3 get请求与post请求的分发 3 1 get请求 3 2 post请求 3 3 通过继
  • MASK R-CNN网络介绍

    目录 前言 一 MASK R CNN网络 1 1 RoIPool和RoIAlign 1 2 MASK分支 二 损失函数 三 Mask分支预测 前言 在介绍MASK R CNN之前 建议先看下FPN网络 Faster R CNN和FCN的介绍
  • python数据可视化入门(五):饼图,环图,极线图,气泡图

    饼图 plt pie x explode None labels None colors None autopct None 1 labels 设置相对应数据的标签 2 labeldistance 设置标签距离圆心的距离 labeldist
  • 【C++ Primer Plus(第6版)中文版第二章开始学习C++(编程练习题)】

    关于个人学习C Primer Plus的第二章编程练习题 1 编写一个C 程序 它显示您的姓名和地址 2 编写一个C 程序 它要求用户输入一个以long为单位的距离 然后将它转换为码 一long等于220码 3 编写一个C 程序 它使用3个
  • CTF_webshow_web11

    d打开靶机 直接有源码 查看一下
  • Java Swing 经典小游戏之 ———— 黄金矿工

    一 效果预览 项目简介 1 游戏预览 2 玩法简介 1 开局一条红绳 左键让他变长 2 5个金块 分成 大中小 3个石块 3 3种速度 空爪 gt 抓回金块 gt 抓回石块 4 4个得分 获得 得分 大金块 30 中金块 20 小金块 10
  • MCU踩坑记录:SWD复用为GPIO使用,Jflash连不上的问题

    用了某款MCU 由于项目需要用的gpio比较多 只能将SWD烧写口SWD CLK和SWD DIO复用为gpio使用 但是程序一旦跑起来 再用jflash就连不上了 如下图所示 如果MCU有BOOT引脚 可以将BOOT引脚配置为其他启动模式不
  • vue脚手架如何创建vue项目

    vue脚手架如何创建vue项目 1 打开命令行 进入需要创建目录的文件夹 或者在需要创建目录的文件夹进入cmd窗口 1 先查看以下vue脚手架是否下载 命令行输入npm list g 1 2 如果没下载vue脚手架 则输入命令npm ins
  • Java开发人员必知必会的20种常用类库和API

    Java开发人员必知必会的20种常用类库和API 一个有经验的Java开发人员特征之一就是善于使用已有的轮子来造车 Effective Java 的作者Joshua Bloch曾经说过 建议使用现有的API来开发 而不是重复造轮子 在本文中
  • Spring cloud Gateway常用配置

    gateway里的常用配置 前面的例子是通过路径 Path 方式配置路由转发 gateway还还有其他的配置 下面做个简单的介绍 1 路由 route Route 主要由 路由id 目标uri 断言集合 过滤器集合组成 前面的实例用到了id
  • python 获取网页视频

    代码实现 import tkinter import tkinter messagebox import os import os path import you get 获取当前工作目录 path os getcwd 设置当前目录为工作目
  • LeetCode 460. LFU Cache

    原题网址 https leetcode com problems lfu cache Design and implement a data structure for Least Frequently Used LFU cache It
  • 吐血推荐那些提升开发人员工作效率的在线工具

    来源 公众号 作者 Hollis 作为一个Java开发人员 经常要和各种各样的工具打交道 除了我们常用的IDE工具以外 其实还有很多工具是我们在日常开发及学习过程中要经常使用到的 Hollis偏爱使用在线工具 因为个人觉得这样比较方便 本文
  • crontab定时启动scrapy爬虫

    部署到测试环境 并设置定时启动任务 通过FileZille工具 将本地的文件上传到测试服务器上面的python文件夹中 在与spider同级目录下写一个shell脚本 启动所编辑的运行爬虫文件 vi run sh 1 bin sh 2 ex
  • 修改服务器数据库名称,修改服务器数据库名称

    修改服务器数据库名称 内容精选 换一换 弹性云服务器能否ping通华为云关系型数据库实例 如果ping不通 可以查看弹性云服务器和华为云关系型数据库实例是否处于同一个虚拟私有云内 是否使用同一个安全组 如果ping不通 可以查看弹性云服务器
  • Aviator这么丝滑,怎么实现的呢?

    大家好 我是老三 在上期 里我们介绍了轻量级规则引擎AviatorScript的基本用法和一些使用案例 这期我们来研究一下 这么丝滑的规则脚本是怎么实现的 概览 我们先来回顾一个简单的例子 Test public void test 表达式
Powered by Hwhale