• QLExpress代码解读,运行原理解析


    简介: 本文针对上图的功能详细图,进行逐个的简单介绍:代码入口、代码的主要逻辑和算法。 调用代码实例 //本文以helloworld案例,开启了两个打印日志的参数,实际使用通常不建议打开。 boolean printParseLog = true;//语法分析日志开关 boolean printExecu…
    本文针对上图的功能详细图,进行逐个的简单介绍:代码入口、代码的主要逻辑和算法。
    调用代码实例

    //本文以helloworld案例,开启了两个打印日志的参数,实际使用通常不建议打开。
    boolean printParseLog = true;//语法分析日志开关
    boolean printExecuteLog = true;//每一条指令执行开关
    ExpressRunner runner = new ExpressRunner(false, printParseLog);
    IExpressContext<String, Object> context = new DefaultContext<String, Object>();
    String script = "sum=0;for(i=0;i<10;i=i+1){sum=sum+i;};return sum;";
    Object result = runner.execute(script, context, null,
            true, printExecuteLog);
    System.out.println(result);
    //结果输出:45
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    一、自上而下debug代码
    1、整体调用代码入口

    // ExpressRunner.java
    /**
     * 执行一段文本
     * @param expressString 程序文本
     * @param context 执行上下文
     * @param errorList 输出的错误信息List
     * @param isCache 是否使用Cache中的指令集
     * @param isTrace 是否输出详细的执行指令信息
     * @param aLog 输出的log
     * @return
     * @throws Exception
     */
    public Object execute(String expressString, IExpressContext<String,Object> context,
            List<String> errorList, boolean isCache, boolean isTrace, Log aLog)
            throws Exception {
        InstructionSet parseResult = null;
        if (isCache == true) {
            parseResult = expressInstructionSetCache.get(expressString);
            if (parseResult == null) {
                synchronized (expressInstructionSetCache) {
                    parseResult = expressInstructionSetCache.get(expressString);
                    if (parseResult == null) {
                        parseResult = this.parseInstructionSet(expressString);
                        expressInstructionSetCache.put(expressString,
                                parseResult);
                    }
                }
            }
        } else {
            parseResult = this.parseInstructionSet(expressString);
        }
        return  InstructionSetRunner.executeOuter(this,parseResult,this.loader,context, errorList,
                 isTrace,false,aLog,false);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    (1)两个步骤
    综上所述:ExpressRunner.execute()实质分成两个步骤

    //(1)编译成指令集过程:string -> InstructionSet
    parseResult = this.parseInstructionSet(expressString);
    
    //(2)指令集执行过程:InstructionSet + context ->Object
    InstructionSetRunner.executeOuter(this,parseResult,this.loader,context, errorList,isTrace,false,aLog,false)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (2)指令集缓存
    只要调用 ExpressRunner.execute() 的isCache的入参,
    parseInstructionSet调用完的结果就会被缓存在ExpressRunner实例的内部对象里:
    private Map expressInstructionSetCache
    2、脚本编译过程:compile

    //ExpressRunner.java
    /**
         * 解析一段文本,生成指令集合
         * @param text
         * @return
         * @throws Exception
         */
        public InstructionSet parseInstructionSet(String text)
        {
           //(1)token分解
           Word[] words = WordSplit.parse(this.nodeTypeManager.splitWord,express);
           //(2)token解析
           List<ExpressNode> tempList = this.transferWord2ExpressNode(rootExpressPackage,words,selfDefineClass,true);
           //(3)匹配AST语法树
           QLMatchResult result = QLPattern.findMatchStatement(this.nodeTypeManager, this.nodeTypeManager.findNodeType("PROGRAM").getPatternNode(), tempList,0);
           result.getMatchs().get(0).buildExpressNodeTree();
            ExpressNode root =(ExpressNode)result.getMatchs().get(0).getRef();
            resetParent(root,null);
           //(4)生成指令集合
           InstructionSet result = createInstructionSet(root, "main");
           
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    (0)输入文本:
    sum=0;for(i=0;i<10;i=i+1){sum=sum+i;};return sum;

    (1)token分解:
    分解为Word[]:“sum”,”=“,”0“,”;“,“for”,“(”,“i”,…

    (2)token解析:
    Word[]转化为List《ExpressNode》:每一个word变得有意义:常量、变量、符号、分割符号

    (3)匹配AST语法树:
    根据KeyWordDefine4Java.java定义的推导文法匹配成一棵AST(抽象语法树)ExpressNode

    (4)生成指令集合

    1:LoadAttr:sum
    2:LoadData 0
    3:OP : = OPNUMBER[2]
    4:openNewArea
    5:clearDataStack
    6:LoadAttr:i
    7:LoadData 0
    8:OP : = OPNUMBER[2]
    9:clearDataStack
    10:LoadAttr:i
    11:LoadData 10
    12:OP : < OPNUMBER[2]
    13:GoToIf[false,isPop=true] +13
    ......
    29:return [value]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3、脚本执行过程:execute

    //ExpressRunner.java
    /**
     * 批量执行指令集合,指令集间可以共享 变量和函数
    */
      public static Object execute(ExpressRunner runner,InstructionSet sets,ExpressLoader loader,IExpressContext<String,Object> aContext, List<String> errorList,boolean isTrace,boolean isCatchException,boolean isReturnLastData,Log aLog,boolean isSupportDynamicFieldName)throws Exception {
                
        //缓存池中创建context对象
        InstructionSetContext  context = OperateDataCacheManager.fetchInstructionSetContext (true,runner,aContext,loader,isSupportDynamicFieldName);
        //缓存池中获取RunEnvironment对象
        RunEnvironment environmen = OperateDataCacheManager.fetRunEnvironment(set,
                (InstructionSetContext) context, isTrace);
        try {
            //执行每一条指令
            CallResult tempResult = set.excute(environmen, context, errorList,
                    isReturnLastData, aLog);
            if (tempResult.isExit() == true) {
                result = tempResult.getReturnValue();
            }
        } catch (Exception e) {
            //...
        }
        return result;
        
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    二、使用归纳总结
    QLExpress属于弱类型脚本语言,一般不推荐声明局部变量的类型。语法编译阶段不会做类型校验,也不会做方法的参数校验,所以很灵活。
    QLExpress的这套自定义的指令集属于解析执行,RunEnvironment中通过programPoint函数指针的跳转来实现每条指令的逐个计算,通过dataContainer作为栈来存储中间计算结果。
    QLExpress定义的指令类型比较少,粒度比较粗,但是足够完成所有的语法功能。
    QLExpress整个运算过程是通过threadLocal来保证线程安全的。
    QLExpress的脚本编译过程比较耗时,但是是上下文无关的,所以同一个脚本运行缓存之后性能提升非常明显。
    QLExpress指令计算运算过程中,基本不会new新对象,是通过缓存池技术来尽量减少资源的消耗。
    QLExpress的宏,function,Operator,变量是非常开放的,名字可以为中文字符,也可以随意扩展,还可以通过脚本静态分析出包含哪些变量、函数,很方便的进行二次业务开发。
    脚本调用classLoader资源的时候可以import,也可以使用类的全路径,构造函数、静态方法、对象方法、类的字段、函数的不定参数调用统统搞定。
    本文来自
    https://developer.aliyun.com/article/700198

  • 相关阅读:
    CASE2023
    java基于Springboot+vue的地方特色美食小吃文化交流分享网站
    ROS自学笔记十六:URDF优化_xacro文件
    协程概述讲解
    Flink 1.13 源码解析——JobManager启动流程之ResourceManager启动
    UAT 深入指南(用户验收测试)
    2023国赛数学建模D题思路模型代码 高教社杯
    力扣题解22-25
    数据向好,分析师预测美联储GDP或将翻一番?
    132.【MySQL_进阶篇】
  • 原文地址:https://blog.csdn.net/qq_43118086/article/details/132872768