04.仿简道云公式函数实战-QLExpress基础语法

1. 前言

小伙伴大家好,在上一篇文章我们简单初探了QLExpress表达式引擎,我们简单写了一个HelloWorld的程序,并成功的运行期望的结果。在本篇文章中我们来熟悉一下QLExpress的语法,因为我们在后面简道云公式实战的时候,需要用到这些语法。废话少说,直接开始。

2. 基础语法

相信大家也下载了QLExpress的源码,在下载的源码中有一个readme文件,在这个文件中,源码作者已经介绍了语法以及QLExpress的用法。大家可以详细阅读,以及运行相应的测试文件。如图所示:

2.1 操作符和java对象

2.1.1 操作符

在readme说明文件中,我们知道QLExpress支持的操作符有:

kotlin 复制代码
+,-,*,/,<,>,<=,>=,==,!=,<>,%,++,---
in 【类似sql】,like【sql语法】,&&,|| 
支持for,break、continue、if then else 等标准的程序控制逻辑

上述的的操作符,笔者都会写测试用例。我们先从简单的四则运算开始:

2.1.1.1 四则运算

arduino 复制代码
四则运算需求提出:
需求1:编写java程序使用QLExpress表达式引擎,计算"1+2-3*4/5" 表达式的结果,并在控制台打印结果
需求2:编写java程序使用QLExpress表达式引擎,计算"1 加 2 减 3 乘 4 除 5"表达式的结果,并在控制台打印结果

当我们拿到需求的时候,首先第一步我们要明确需求,需求是让我们借助QLExpress表达式引擎,来计算表达式的结果。

表达式中包括英文"+-*/"和中文"加 减 乘 除"。需求中并没有提出计算结果精度的问题(虽然需求没有定下来,我们要支持高精度计算)。

基于上述的讨论后,码农就拿起键盘一头扎进了QLExpress代码学习中,以及各种百度,CSDN搜索解决方案。最终码农A编写了需求1如下代码:

java 复制代码
/***
 * "1+2-3*4/5"
 */
@Test
public void test1() throws Exception{
    // @param isPrecise 是否需要高精度计算支持
    // @param isTrace   是否跟踪执行指令的过程
    ExpressRunner runner = new ExpressRunner(true, true);
    String expressStr = "1+2-3*4/5";
    // isTrace
    object rst = runner.execute(expressStr, null, null, true, true);
    System.out.println(rst);
}

码农A很自豪的讲起了自己的解决方案,QLExpress引擎提供了isPrecise 和 isTrace两个属性,在ExpressRunner 初始化的时候可以指定isPrecise 是否需要高精度计算支持和isTrace 是否跟踪执行指令的过程。isTrace在调试阶段很有用,可以查看表达式执行指令的整个过程,在程序上线时,建议设置为false避免浪费资源。

如果isPrecise=true代码运行的结果为0.6000000000;如果isPrecise = false代码运行结果为1.代码运行如图所示:

码农A沮丧的说,我解决了需求1,但是需求2没有找到解决方案。项目组中有人找到需求2的解决方案吗?大家可以交流一下。一向低调的码农B开口了。

码农B说:通过阅读QLExpress代码,找到一个方法

addOperatorWithAlias("加","+",null)。

kotlin 复制代码
/**
 * 添加操作符和关键字的别名,同时对操作符可以指定错误信息。
 * 例如:addOperatorWithAlias("加","+",null)
 *
 * @param keyWordName
 * @param realKeyWordName
 * @param errorInfo
 * @throws Exception
 */
public void addOperatorWithAlias(String keyWordName, String realKeyWordName, String errorInfo) throws Exception {
    if (errorInfo != null && errorInfo.trim().length() == 0) {
        errorInfo = null;
    }
    //添加函数别名
    if (this.manager.isFunction(realKeyWordName)) {
        this.manager.addFunctionName(keyWordName);
        this.operatorManager.addOperatorWithAlias(keyWordName, realKeyWordName, errorInfo);
        return;
    }
    NodeType realNodeType = this.manager.findNodeType(realKeyWordName);
    if (realNodeType == null) {
        throw new QLException("关键字:" + realKeyWordName + "不存在");
    }
    boolean isExist = this.operatorManager.isExistOperator(realNodeType.getName());
    if (!isExist && errorInfo != null) {
        throw new QLException("关键字:" + realKeyWordName + "是通过指令来实现的,不能设置错误的提示信息,errorInfo 必须是 null");
    }
    if (!isExist || errorInfo == null) {
        //不需要新增操作符号,只需要建立一个关键子即可
        this.manager.addOperatorWithRealNodeType(keyWordName, realNodeType.getName());
    } else {
        this.manager.addOperatorWithLevelOfReference(keyWordName, realNodeType.getName());
        this.operatorManager.addOperatorWithAlias(keyWordName, realNodeType.getName(), errorInfo);
    }
}

此方法作用就是添加操作符和关键字的别名,于是就构思出来如下代码**【test2方法运行报错】**:

java 复制代码
@Test
public void test2() throws Exception{
    ExpressRunner runner = new ExpressRunner(true, true);
    String expressStr = "1 加 2 减 3 乘 4 除 5";
    runner.addOperatorWithAlias("加","+","加 的这个操作符有问题");
    runner.addOperatorWithAlias("减","-","减 的这个操作符有问题");
    runner.addOperatorWithAlias("乘","*","乘 的这个操作符有问题");
    runner.addOperatorWithAlias("除","/","除 的这个操作符有问题");
    // isTrace
    object rst = runner.execute(expressStr, null, null, true, true);
    System.out.println(rst);
}

经测试发现程序报错:

com.ql.util.express.exception.QLException: * 不能被设置别名**:com.ql.util.express.instruction.op.OperatorMultiplyDivide.**(java.lang.String, java.lang.String, java.lang.String) 如下图所示:

通过报错信息可知OperatorMultiplyDivide.方法有问题,于是就进入OperatorMultiplyDivide源码,模仿着写了

OperatorMultiply 类和 OperatorDivide类,即把 OperatorMultiplyDivide类拆分成两个类。

scala 复制代码
/**
 * 类描述:乘操作
 *
 * @author admin
 * @version 1.0.0
 * @date 2023/11/13 17:17
 */
public class OperatorMultiply extends Operator {

    @Override
    public object executeInner(object[] list) throws Exception {
        return executeInner(list[0], list[1]);
    }

    public object executeInner(object op1, object op2) throws Exception {
        object result = OperatorOfNumber.multiply(op1, op2, this.isPrecise);
        return result;
    }

}

/**
 * 类描述:除操作
 *
 * @author admin
 * @version 1.0.0
 * @date 2023/11/13 17:21
 */
public class OperatorDivide extends Operator {

    @Override
    public object executeInner(object[] list) throws Exception {
        return executeInner(list[0], list[1]);
    }

    public object executeInner(object op1, object op2) throws Exception {
        object result = OperatorOfNumber.divide(op1, op2, this.isPrecise);
        return result;
    }

}

基于上面两个类,又写了test3方法**(该方法运行报错)**

java 复制代码
@Test
public void test3() throws Exception{
    ExpressRunner runner = new ExpressRunner(true, true);
    String expressStr = "1 加 2 减 3 乘 4 除 5";
    runner.addOperator("*",new OperatorMultiply());
    runner.addOperator("/",new OperatorDivide());
    runner.addOperatorWithAlias("加","+","加 的这个操作符有问题");
    runner.addOperatorWithAlias("减","-","减 的这个操作符有问题");
    runner.addOperatorWithAlias("乘","*","乘 的这个操作符有问题");
    runner.addOperatorWithAlias("除","/","除 的这个操作符有问题");
    // isTrace
    object rst = runner.execute(expressStr, null, null, true, true);
    System.out.println(rst);
}

运行报错:java.lang.RuntimeException: 节点类型定义重复:* 定义1=*:TYPE=KEYWORD 定义2=*:TYPE=KEYWORD

根据错误信息猜测应该是runner.addOperator("*" ,new OperatorMultiply());源码中已经有了,于是就找到OperatorFactory工厂类(笔者删减了其他代码)发现确实在这个工厂类中已经添加addOperator("*", new OperatorMultiplyDivide("*"));addOperator("/", new OperatorMultiplyDivide("/"));

arduino 复制代码
public OperatorFactory(boolean isPrecise) {
    this.isPrecise = isPrecise;
    addOperator("!", new OperatorNot("!"));
    addOperator("*", new OperatorMultiplyDivide("*"));
    addOperator("/", new OperatorMultiplyDivide("/"));
}

既然有了那我就直接用汉字 不就能满足需求了,于是就又写了以下代码test4**(运行通过)**

java 复制代码
@Test
public void test4() throws Exception{
    ExpressRunner runner = new ExpressRunner(true, true);
    String expressStr = "1 加 2 减 3 乘 4 除 5";
    runner.addOperator("乘",new OperatorMultiply());
    runner.addOperator("除",new OperatorDivide());
    runner.addOperatorWithAlias("加","+","加 的这个操作符有问题");
    runner.addOperatorWithAlias("减","-","减 的这个操作符有问题");
    // isTrace
    object rst = runner.execute(expressStr, null, null, true, true);
    System.out.println(rst);
}

如果isPrecise=true代码运行的结果为0.6000000000;如果isPrecise = false代码运行结果为1

相关推荐
Victor35616 分钟前
MongoDB(87)如何使用GridFS?
后端
Victor35619 分钟前
MongoDB(88)如何进行数据迁移?
后端
小红的布丁35 分钟前
单线程 Redis 的高性能之道
redis·后端
GetcharZp41 分钟前
Go 语言只能写后端?这款 2D 游戏引擎刷新你的认知!
后端
宁瑶琴2 小时前
COBOL语言的云计算
开发语言·后端·golang
普通网友2 小时前
阿里云国际版服务器,真的是学生党的性价比之选吗?
后端·python·阿里云·flask·云计算
IT_陈寒3 小时前
Vue的这个响应式问题,坑了我整整两小时
前端·人工智能·后端
Soofjan4 小时前
Go 内存回收-GC 源码1-触发与阶段
后端
shining4 小时前
[Golang]Eino探索之旅-初窥门径
后端
掘金者阿豪4 小时前
Mac 程序员效率神器:6 个我每天都在用的 Mac 工具推荐(Alfred / Paste / PixPin / HexHub / iTerm2 /)
后端