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

相关推荐
夜月行者1 小时前
如何使用ssm实现基于SSM的宠物服务平台的设计与实现+vue
java·后端·ssm
Yvemil71 小时前
RabbitMQ 入门到精通指南
开发语言·后端·ruby
sdg_advance1 小时前
Spring Cloud之OpenFeign的具体实践
后端·spring cloud·openfeign
猿java2 小时前
使用 Kafka面临的挑战
java·后端·kafka
碳苯2 小时前
【rCore OS 开源操作系统】Rust 枚举与模式匹配
开发语言·人工智能·后端·rust·操作系统·os
kylinxjd2 小时前
spring boot发送邮件
java·spring boot·后端·发送email邮件
2401_857439695 小时前
Spring Boot新闻推荐系统:用户体验优化
spring boot·后端·ux
进击的女IT6 小时前
SpringBoot上传图片实现本地存储以及实现直接上传阿里云OSS
java·spring boot·后端
一 乐8 小时前
学籍管理平台|在线学籍管理平台系统|基于Springboot+VUE的在线学籍管理平台系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·学习
艾伦~耶格尔10 小时前
Spring Boot 三层架构开发模式入门
java·spring boot·后端·架构·三层架构