From a33935cd6e693eb71cb34e9a0d6b299b09559d47 Mon Sep 17 00:00:00 2001
From: DQinYuan <932087612@qq.com>
Date: Sun, 29 Dec 2024 22:37:16 +0800
Subject: [PATCH] readme adoc
---
README-source.adoc | 342 +++++++++-
README.md | 587 ------------------
pom.xml | 2 +-
src/main/antlr4/QLParser.g4 | 2 +-
src/main/antlr4/QLexer.g4 | 6 +-
.../qlexpress4/Express4RunnerTest.java | 32 +-
.../alibaba/qlexpress4/TestSuiteRunner.java | 2 +-
.../aparser/SyntaxTreeFactoryTest.java | 4 +-
.../doc/convenient_syntax_elements.ql | 16 +
.../independent/doc/dynamic_string.ql | 9 +
.../independent/doc/dynamic_typing.ql | 8 +
.../testsuite/independent/doc/for.ql | 5 +
.../testsuite/independent/doc/for_each.ql | 8 +
.../testsuite/independent/doc/function.ql | 4 +
.../resources/testsuite/independent/doc/if.ql | 8 +
.../testsuite/independent/doc/if_as_expr.ql | 5 +
.../testsuite/independent/doc/lambda.ql | 4 +
.../independent/doc/list_map_filter.ql | 3 +
.../testsuite/independent/doc/try_catch.ql | 6 +
.../independent/doc/try_catch_as_expr.ql | 6 +
.../testsuite/independent/doc/while.ql | 7 +
.../independent/string/interpolation.ql | 14 +-
22 files changed, 473 insertions(+), 607 deletions(-)
delete mode 100644 README.md
create mode 100644 src/test/resources/testsuite/independent/doc/convenient_syntax_elements.ql
create mode 100644 src/test/resources/testsuite/independent/doc/dynamic_string.ql
create mode 100644 src/test/resources/testsuite/independent/doc/dynamic_typing.ql
create mode 100644 src/test/resources/testsuite/independent/doc/for.ql
create mode 100644 src/test/resources/testsuite/independent/doc/for_each.ql
create mode 100644 src/test/resources/testsuite/independent/doc/function.ql
create mode 100644 src/test/resources/testsuite/independent/doc/if.ql
create mode 100644 src/test/resources/testsuite/independent/doc/if_as_expr.ql
create mode 100644 src/test/resources/testsuite/independent/doc/lambda.ql
create mode 100644 src/test/resources/testsuite/independent/doc/list_map_filter.ql
create mode 100644 src/test/resources/testsuite/independent/doc/try_catch.ql
create mode 100644 src/test/resources/testsuite/independent/doc/try_catch_as_expr.ql
create mode 100644 src/test/resources/testsuite/independent/doc/while.ql
diff --git a/README-source.adoc b/README-source.adoc
index a2630757..3e7e820f 100644
--- a/README-source.adoc
+++ b/README-source.adoc
@@ -1,4 +1,6 @@
-= QLExpress4
+:toc:
+
+= QLExpress
== 背景介绍
@@ -30,7 +32,7 @@ QLExpress4 作为 QLExpress 的最新演进版本,基于 Antlr4 重写了解
com.alibaba
QLExpress4
- 4.0.0-beta
+ 4.0.0-beta.2
----
@@ -39,4 +41,338 @@ QLExpress4 作为 QLExpress 的最新演进版本,基于 Antlr4 重写了解
[source,java,indent=0]
----
include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=firstQl]
-----
\ No newline at end of file
+----
+
+=== 添加自定义函数与操作符
+
+最简单的方式是通过 Java Lambda 表达式快速定义函数/操作符的逻辑:
+
+[source,java,indent=0]
+----
+include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=addFunctionAndOperator]
+----
+
+如果自定义函数的逻辑比较复杂,或者需要获得脚本的上下文信息,也可以通过继承 `CustomFunction` 的方式实现。
+
+比如下面的 `hello` 自定义函数,根据租户不同,返回不同的欢迎信息:
+
+[source,java,indent=0]
+----
+include::./src/test/java/com/alibaba/qlexpress4/test/function/HelloFunction.java[]
+----
+
+[source,java,indent=0]
+----
+include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=customComplexFunction]
+----
+
+=== 高精度计算
+
+QLExpress 内部会用 BigDecimal 表示所有无法用 double 精确表示数字,来尽可能地表示计算精度:
+
+> 举例:0.1 在 double 中无法精确表示
+
+[source,java,indent=0]
+----
+include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=bigDecimalForPrecise]
+----
+
+通过这种方式能够解决一些计算精度问题:
+
+比如 0.1+0.2 因为精度问题,在 Java 中是不等于 0.3 的。
+而 QLExpress 能够自动识别出 0.1 和 0.2 无法用双精度精确表示,改成用 BigDecimal 表示,确保其结果等于0.3
+
+[source,java,indent=0]
+----
+include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=preciseComparisonWithJava]
+----
+
+除了默认的精度保证外,还提供了 `precise` 开关,打开后所有的计算都使用BigDecimal,防止外部传入的低精度数字导致的问题:
+
+[source,java,indent=0]
+----
+include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=preciseSwitch]
+----
+
+=== 调用应用中的 Java 类
+
+> 需要放开安全策略,不建议用于终端用户输入
+
+假设应用中有如下的 Java 类(`com.alibaba.qlexpress4.QLImportTester`):
+
+[source,java,indent=0]
+----
+include::./src/test/java/com/alibaba/qlexpress4/QLImportTester.java[]
+----
+
+在 QLExpress 中有如下两种调用方式。
+
+==== 1. 在脚本中使用 `import` 语句导入类并且使用
+
+[source,java,indent=0]
+----
+include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=importJavaCls]
+----
+
+==== 2. 在创建 `Express4Runner` 时默认导入该类,此时脚本中就不需要额外的 `import` 语句
+
+[source,java,indent=0]
+----
+include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=defaultImport]
+----
+
+=== 表达式缓存
+
+通过 `cache` 选项可以开启表达式缓存,这样相同的表达式就不会重新编译,能够大大提升性能。
+
+注意该缓存没有限制大小,只适合在表达式为有限数量的情况下使用:
+
+[source,java,indent=0]
+----
+include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=cacheSwitch]
+----
+
+=== 扩展函数
+
+利用 QLExpress 提供的扩展函数能力,可以给Java类中添加额外的成员方法。
+
+扩展函数是基于 QLExpress 运行时实现的,因此仅仅在 QLExpress 脚本中有效。
+
+下面的示例代码给 String 类添加了一个 `hello()` 扩展函数:
+
+[source,java,indent=0]
+----
+include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=extensionFunction]
+----
+
+=== 对象,字段和函数别名
+
+QLExpress 支持通过 `QLAlias` 注解给对象,字段或者函数定义一个或多个别名,方便非技术人员使用表达式定义规则。
+
+下面的例子中,根据用户是否 vip 计算订单最终金额。
+
+用户类定义:
+
+[source,java,indent=0]
+----
+include::./src/test/java/com/alibaba/qlexpress4/test/qlalias/User.java[]
+----
+
+订单类定义:
+
+[source,java,indent=0]
+----
+include::./src/test/java/com/alibaba/qlexpress4/test/qlalias/Order.java[]
+----
+
+通过 QLExpress 脚本规则计算最终订单金额:
+
+[source,java,indent=0]
+----
+include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=qlAlias]
+----
+
+== 语法入门
+
+QLExpress4 兼容 Java8 语法的同时,也提供了很多更加灵活宽松的语法模式,帮助用户更快捷地编写表达式。
+
+基于表达式优先的语法设计,复杂的条件判断语句也可以直接当作表达式使用。
+
+在本章节中出现的代码片段都是 qlexpress 脚本,
+`assert` 是测试框架往引擎中注入的断言方法,会确保其参数为 `true`。
+`assertErrCode` 会确保其 lambda 参数表达式的执行一定会抛出含第二个参数 error code 的 QLException。
+
+=== 变量声明
+
+同时支持静态类型和动态类型:
+
+ * 变量声明时不写类型,则变量是动态类型,也同时是一个赋值表达式
+ * 变量声明如果写类型,则是静态类型,此时是一个变量声明语句
+
+[source,java]
+----
+include::./src/test/resources/testsuite/independent/doc/dynamic_typing.ql[]
+----
+
+=== 方便语法元素
+
+列表(List),映射(Map)等常用语法元素在 QLExpress 中都有非常方便的构造语法糖:
+
+[source,java]
+----
+include::./src/test/resources/testsuite/independent/doc/convenient_syntax_elements.ql[]
+----
+
+=== 数字
+
+对于未声明类型的数字,
+QLExpress会根据其所属范围自动从 int, long, BigInteger, double, BigDecimal 等数据类型中选择一个最合适的:
+
+[source,java]
+----
+include::./src/test/resources/testsuite/java/number/number_auto_type.ql[]
+----
+
+因此在自定义函数或者操作符时,建议使用 Number 类型进行接收,因为数字类型是无法事先确定的。
+
+=== 动态字符串
+
+QLExpress 支持 `$\{expression}` 的格式在字符串中插入表达式,更方便地进行动态字符串组装:
+
+
+[source,java]
+----
+include::./src/test/resources/testsuite/independent/doc/dynamic_string.ql[]
+----
+
+=== 分号
+
+表达式语句可以省略结尾的分号,整个脚本的返回值就是最后一个表达式的计算结果。
+
+以下脚本的返回值为 2:
+
+[source,java]
+----
+a = 1
+b = 2
+// last express
+1+1
+----
+
+等价于以下写法:
+
+[source,java]
+----
+a = 1
+b = 2
+// return statment
+return 1+1;
+----
+
+=== 表达式
+
+QLExpress 采用表达式优先的设计,其中 除了 import, return 和循环等结构外,几乎都是表达式。
+
+if 语句也是一个表达式:
+
+[source,java]
+----
+include::./src/test/resources/testsuite/independent/doc/if_as_expr.ql[]
+----
+
+try catch 结构也是一个表达式:
+
+[source,java]
+----
+include::./src/test/resources/testsuite/independent/doc/try_catch_as_expr.ql[]
+----
+
+=== 控制结构
+
+==== if 分支
+
+[source,java]
+----
+include::./src/test/resources/testsuite/independent/doc/if.ql[]
+----
+
+==== while 循环
+
+[source,java]
+----
+include::./src/test/resources/testsuite/independent/doc/while.ql[]
+----
+
+==== for 循环
+
+[source,java]
+----
+include::./src/test/resources/testsuite/independent/doc/for.ql[]
+----
+
+==== for-each 循环
+
+[source,java]
+----
+include::./src/test/resources/testsuite/independent/doc/for_each.ql[]
+----
+
+==== try-catch
+
+[source,java]
+----
+include::./src/test/resources/testsuite/independent/doc/try_catch.ql[]
+----
+
+==== 函数定义
+
+[source,java]
+----
+include::./src/test/resources/testsuite/independent/doc/function.ql[]
+----
+
+==== Lambda 表达式
+
+QLExpress4 中,Lambda 表达式作为一等公民,可以作为变量进行传递或者返回。
+
+[source,java]
+----
+include::./src/test/resources/testsuite/independent/doc/lambda.ql[]
+----
+
+==== 列表过滤和映射
+
+支持通过 filter, map 方法直接对列表类型进行函数式过滤和映射。
+
+底层通过在列表类型添加 link:#_扩展函数[扩展函数] 实现,注意和 Stream API 中同名方法区分。
+
+相比 Stream Api,它可以直接对列表进行操作,返回值也直接就是列表,更加方便。
+
+[source,java]
+----
+include::./src/test/resources/testsuite/independent/doc/list_map_filter.ql[]
+----
+
+==== 兼容 Java8 语法
+
+QLExpress 可以兼容 Java8 的常见语法。
+
+比如 link:#_for_each_循环[for each循环], Stream API, 函数式接口等等。
+
+===== Stream API
+
+可以直接使用 Java 集合中的 stream api 对集合进行操作。
+
+因为此时的 stream api 都是来自 Java 中的方法,参考 link:#_调用应用中的_java_类[调用应用中的Java类] 打开安全选项,以下脚本才能正常执行。
+
+[source,java]
+----
+include::./src/test/resources/testsuite/java/stream/java_stream.ql[]
+----
+
+===== 函数式接口
+
+Java8 中引入了 Function, Consumer, Predicate 等函数式接口,QLExpress 中的 link:#_lambda_表达式[Lambda表达式] 可以赋值给这些接口,或者作为接收这些接口的方法参数:
+
+[source,java]
+----
+include::./src/test/resources/testsuite/java/lambda/java_functional_interface.ql[]
+----
+
+== 附录一 QLExpress4性能提升
+
+link:https://www.yuque.com/xuanheng-ffjti/iunlps/pgfzw46zel2xfnie?singleDoc#%20%E3%80%8AQLExpress3%E4%B8%8E4%E6%80%A7%E8%83%BD%E5%AF%B9%E6%AF%94%E3%80%8B[QLExpress4与3性能对比]
+
+总结:常见场景下,无编译缓存时,QLExpress4能比3有接近10倍性能提升;有编译缓存,也有一倍性能提升。
+
+== 附录二 开发者联系方式
+
+ * email:
+ ** qinyuan.dqy@alibaba-inc.com
+ ** yumin.pym@taobao.com
+ ** 704643716@qq.com
+ * wechat:
+ ** xuanheng: dqy932087612
+ ** binggou: pymbupt
+ ** linxiang: tkk33362
+
diff --git a/README.md b/README.md
deleted file mode 100644
index cd8ccb77..00000000
--- a/README.md
+++ /dev/null
@@ -1,587 +0,0 @@
-# 背景介绍
-
-由阿里的电商业务规则演化而来的嵌入式Java动态脚本工具,在阿里集团有很强的影响力,同时为了自身不断优化、发扬开源贡献精神,于2012年开源。
-
-在基本的表达式计算的基础上,增加以下特色功能:
-
- - 灵活的自定义能力,通过 Java API 自定义函数和操作符,可以快速实现业务规则的 DSL
- - 兼容Java语法,最新的QLExpress4可以兼容Java8语法,方便Java程序员快速熟悉
- - 默认安全,脚本默认不允许和应用代码进行交互,如果需要交互,也可以自行定义安全的交互方式
- - 解释执行,不占用 JVM 元空间,可以开启缓存提升解释性能
-
-QLExpress4 作为 QLExpress 的最新演进版本,基于 Antlr4 重写了解析引擎,将原先的优点进一步发扬光大,彻底拥抱Java8和函数式编程,在性能和表达式能力上都进行了进一步增强。
-场景举例:
-
- - 电商优惠券规则配置:通过 QLExpress 自定义函数和操作符快速实现优惠规则 DSL,供运营人员根据需求自行动态配置
- - 表单搭建控件关联规则配置:表单搭建平台允许用户拖拽控件搭建自定义的表单,利用 QLExpress 脚本配置不同控件间的关联关系
- - 流程引擎条件规则配置
- - 广告系统计费规则配置
-......
-
-# API 快速入门
-
-## 引入依赖
-
-```xml
-
- com.alibaba
- QLExpress4
- 4.0.0-beta
-
-```
-
-## 第一个 QLExpress 程序
-
-```java
-Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
-Map context = new HashMap<>();
-context.put("a", 1);
-context.put("b", 2);
-context.put("c", 3);
-Object result = express4Runner.execute("a + b * c", context, QLOptions.DEFAULT_OPTIONS);
-assertEquals(7, result);
-```
-
-## 添加自定义函数与操作符
-
-最简单的方式是通过 Java Lambda 表达式快速定义函数/操作符的逻辑:
-
-```java
-Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
-// 自定义函数
-express4Runner.addVarArgsFunction("join", params ->
- Arrays.stream(params).map(Object::toString).collect(Collectors.joining(",")));
-Object resultFunction = express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS);
-assertEquals("1,2,3", resultFunction);
-
-// 自定义操作符
-express4Runner.addOperatorBiFunction("join", (left, right) -> left + "," + right);
-Object resultOperator = express4Runner.execute("1 join 2 join 3", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS);
-assertEquals("1,2,3", resultOperator);
-```
-
-如果自定义函数的逻辑比较复杂,或者需要获得脚本的上下文信息,也可以通过继承 `CustomFunction` 的方式实现。
-
-比如下面的 `hello` 自定义函数,根据租户不同,返回不同的欢迎信息:
-
-```java
-import com.alibaba.qlexpress4.runtime.Parameters;
-import com.alibaba.qlexpress4.runtime.QContext;
-import com.alibaba.qlexpress4.runtime.function.CustomFunction;
-
-public class HelloFunction implements CustomFunction {
- @Override
- public Object call(QContext qContext, Parameters parameters) throws Throwable {
- String tenant = (String) qContext.attachment().get("tenant");
- return "hello," + tenant;
- }
-}
-```
-
-```java
-Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
-express4Runner.addFunction("hello", new HelloFunction());
-String resultJack = (String) express4Runner.execute("hello()", Collections.emptyMap(), QLOptions.builder()
- .attachments(Collections.singletonMap("tenant", "jack")).build());
-assertEquals("hello,jack", resultJack);
-String resultLucy = (String) express4Runner.execute("hello()", Collections.emptyMap(), QLOptions.builder()
- .attachments(Collections.singletonMap("tenant", "lucy")).build());
-assertEquals("hello,lucy", resultLucy);
-```
-
-## 高精度计算
-
-QLExpress 内部会用 BigDecimal 表示所有无法用 double 精确表示数字,来尽可能地表示计算精度:
-
-> 0.1 在 double 中无法精确表示
-
-```java
-Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
-Object result = express4Runner.execute("0.1", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS);
-assertTrue(result instanceof BigDecimal);
-```
-
-通过这种方式能够解决一些计算精度问题:
-
-比如 0.1+0.2 因为精度问题,在 Java 中是不等于 0.3 的。
-而 QLExpress 能够自动识别出 0.1 和 0.2 无法用双精度精确表示,改成用 BigDecimal 表示,确保其结果等于0.3
-
-```java
-assertNotEquals(0.3, 0.1 + 0.2, 0.0);
-assertTrue((Boolean) express4Runner.execute("0.3==0.1+0.2", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS));
-```
-
-除了默认的精度保证外,还提供了 `precise` 开关,打开后所有的计算都使用BigDecimal,防止外部传入的低精度数字导致的问题:
-
-```java
-Map context = new HashMap<>();
-context.put("a", 0.1);
-context.put("b", 0.2);
-assertFalse((Boolean) express4Runner.execute("0.3==a+b", context, QLOptions.DEFAULT_OPTIONS));
-// open precise switch
-assertTrue((Boolean) express4Runner.execute("0.3==a+b", context, QLOptions.builder().precise(true).build()));
-```
-
-## 调用应用中的 Java 类
-
-> 需要放开安全策略,不建议用于终端用户输入
-
-假设应用中有如下的 Java 类(`com.alibaba.qlexpress4.QLImportTester`):
-
-```java
-package com.alibaba.qlexpress4;
-
-public class QLImportTester {
-
- public static int add(int a, int b) {
- return a + b;
- }
-
-}
-```
-
-在 QLExpress 中有如下两种调用方式。
-
- 1. 在脚本中使用 `import` 语句导入类并且使用
-
-```java
-Express4Runner express4Runner = new Express4Runner(InitOptions.builder()
- // open security strategy, which allows access to all Java classes within the application.
- .securityStrategy(QLSecurityStrategy.open())
- .build()
-);
-// Import Java classes using the import statement.
-Object result = express4Runner.execute("import com.alibaba.qlexpress4.QLImportTester;" +
- "QLImportTester.add(1,2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS);
-Assert.assertEquals(3, result);
-```
-
- 2. 在创建 `Express4Runner` 时默认导入该类,此时脚本中就不需要额外的 `import` 语句
-
-```java
-Express4Runner express4Runner = new Express4Runner(InitOptions.builder()
- .defaultImport(
- Collections.singletonList(ImportManager.importCls("com.alibaba.qlexpress4.QLImportTester"))
- )
- .securityStrategy(QLSecurityStrategy.open())
- .build()
-);
-Object result = express4Runner.execute("QLImportTester.add(1,2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS);
-Assert.assertEquals(3, result);
-```
-
-## 表达式缓存
-
-通过 `cache` 选项可以开启表达式缓存,这样相同的表达式就不会重新编译,能够大大提升性能。
-
-注意该缓存没有限制大小,只适合在表达式为有限数量的情况下使用:
-
-```java
-Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
-// open cache switch
-express4Runner.execute("1+2", new HashMap<>(), QLOptions.builder()
- .cache(true).build());
-```
-
-## 扩展函数
-
-利用 QLExpress 提供的扩展函数能力,可以给Java类中添加额外的成员方法。
-
-扩展函数是基于 QLExpress 运行时实现的,因此仅仅在 QLExpress 脚本中有效。
-
-下面的示例代码给 String 类添加了一个 `hello()` 扩展函数:
-
-```java
- ExtensionFunction helloFunction = new ExtensionFunction() {
- @Override
- public Class>[] getParameterTypes() {
- return new Class[0];
- }
-
- @Override
- public String getName() {
- return "hello";
- }
-
- @Override
- public Class> getDeclaringClass() {
- return String.class;
- }
-
- @Override
- public Object invoke(Object obj, Object[] args) throws InvocationTargetException, IllegalAccessException {
- String originStr = (String) obj;
- return "Hello," + originStr;
- }
- };
- Express4Runner express4Runner = new Express4Runner(InitOptions.builder()
- .addExtensionFunctions(Collections.singletonList(helloFunction))
- .build());
- Object result = express4Runner.execute("'jack'.hello()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS);
- assertEquals("Hello,jack", result);
-```
-
-## 对象,字段和函数别名
-
-QLExpress 支持通过 `QLAlias` 注解给对象,字段或者函数定义一个或多个别名,方便非技术人员使用表达式定义规则。
-
-下面的例子中,根据用户是否 vip 计算订单最终金额。
-
-用户类定义:
-
-```java
-import com.alibaba.qlexpress4.annotation.QLAlias;
-
-@QLAlias("用户")
-public class User {
-
- @QLAlias("是vip")
- private boolean vip;
-
- @QLAlias("用户名")
- private String name;
-
- public boolean isVip() {
- return vip;
- }
-
- public void setVip(boolean vip) {
- this.vip = vip;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-}
-```
-
-订单类定义:
-
-```java
-import com.alibaba.qlexpress4.annotation.QLAlias;
-
-@QLAlias("订单")
-public class Order {
-
- @QLAlias("订单号")
- private String orderNum;
-
- @QLAlias("金额")
- private int amount;
-
- public String getOrderNum() {
- return orderNum;
- }
-
- public void setOrderNum(String orderNum) {
- this.orderNum = orderNum;
- }
-
- public int getAmount() {
- return amount;
- }
-
- public void setAmount(int amount) {
- this.amount = amount;
- }
-}
-```
-
-通过 QLExpress 脚本规则计算最终订单金额:
-
-```java
- Order order = new Order();
- order.setOrderNum("OR123455");
- order.setAmount(100);
-
- User user = new User();
- user.setName("jack");
- user.setVip(true);
-
- // Calculate the Final Order Amount
- Express4Runner express4Runner = new Express4Runner(InitOptions.builder()
- .securityStrategy(QLSecurityStrategy.open()).build());
- Number result = (Number) express4Runner.executeWithAliasObjects("用户.是vip? 订单.金额 * 0.8 : 订单.金额",
- QLOptions.DEFAULT_OPTIONS, order, user);
- assertEquals(80, result.intValue());
-```
-
-# 语法入门
-
-QLExpress4 兼容 Java8 语法的同时,也提供了很多更加灵活宽松的语法模式,帮助用户更快捷地编写表达式。
-
-基于表达式优先的语法设计,复杂的条件判断语句也可以直接当作表达式使用。
-
-## 变量声明
-
-同时支持静态类型和动态类型:
-
- - 变量声明时不写类型,则变量是动态类型,也同时是一个赋值表达式
- - 变量声明如果写类型,则是静态类型,此时是一个变量声明语句
-
-```java
-// Dynamic Typeing
-a = 1;
-// Static Typing
-int b = 2;
-```
-
-## 方便语法元素
-
-列表,映射等常用语法元素在 Java 里构造过于麻烦,因此在 QLExpress 中对其进行了简化
-
-```qlexpress
-// list
-l = [1,2,3]
-// map
-m = {
- "aa": 10,
- "bb": {
- "cc": "cc1",
- "dd": "dd1"
- }
-}
-```
-
-列表的实际数据类型是 Java 中的 ArrayList,映射的底层数据类型是 LinkedHashMap
-
-## 数字
-
-对于未声明类型的数字,
-QLExpress会根据其所属范围自动从 int, long, BigInteger, BigDecimal 等数据类型中选择一个最合适的:
-
-```qlexpress
-// true
-2147483647 instanceof Integer
-// true
-9223372036854775807 instanceof Long
-// true
-18446744073709552000 instanceof BigInteger
-```
-
-因此在自定义函数或者操作符时,建议使用 Number 类型进行接收,因为数字类型是无法事先确定的。
-
-## 动态字符串
-
-QLExpress 支持 `${expression$}` 的格式在字符串中插入表达式,更方便地进行动态字符串组装:
-
-```qlexpress
-a = 123;
-// Output: hello,122
-"hello,${a-1$}"
-```
-
-```qlexpress
-b = "test"
-// Output: m xx YYY
-"m xx ${
- if (b like 't%') {
- "YYY"
- }
-$}"
-```
-
-## 分号
-
-表达式语句可以省略结尾的分号,整个脚本的返回值就是最后一个表达式的计算结果。
-
-以下脚本的返回值为 2:
-
-```qlexpress
-a = 1
-b = 2
-// last express
-1+1
-```
-
-等价于以下写法:
-
-```qlexpress
-a = 1
-b = 2
-// return statment
-return 1+1;
-```
-
-## 表达式
-
-QLExpress 采用表达式优先的设计,其中 除了 import, return 和循环等结构外,几乎都是表达式。
-
-if 语句也是一个表达式,以下脚本的输出为 11:
-
-```java
-if (11 == 11) {
- 10
-} else {
- 20 + 2
-} + 1;
-```
-
-try catch 结构也是一个表达式,以下脚本的输出为 12:
-
-```java
-1 + try {
- 100 + 1/0
-} catch(e) {
- // Throw a zero-division exception
- 11
-}
-```
-
-## 控制结构
-
-### if 分支
-
-以下脚本的输出为 true:
-
-```qlexpress
-a = 11;
-if (a >= 0 && a < 5) {
- true
-} else if (a >= 5 && a < 10) {
- false
-} else if (a >= 10 && a < 15) {
- true
-}
-```
-
-### while 循环
-
-以下脚本的输出为 2:
-
-```qlexpress
-i = 0;
-while (i < 5) {
- if (++i == 2) {
- break;
- }
-}
-i
-```
-
-### for 循环
-
-以下脚本的输出为列表 `[3,4,5]`:
-
-```qlexpress
-l = [];
-for (int i = 3; i < 6; i++) {
- l.add(i);
-}
-l
-```
-
-### for-each 循环
-
-以下脚本的输出为 8:
-
-```qlexpress
-sum = 0;
-for (i: [0,1,2,3,4]) {
- if (i == 2) {
- continue;
- }
- sum += i;
-}
-sum
-```
-
-### try-catch
-
-以下脚本的输出为 11:
-
-```qlexpress
-try {
- 100 + 1/0
-} catch(e) {
- // Throw a zero-division exception
- 11
-}
-```
-
-### 函数定义
-
-以下脚本的输出为 2:
-
-```qlexpress
-function sub(a, b) {
- return a-b;
-}
-sub(3,1)
-```
-
-### Lambda表达式
-
-QLExpress4 中,Lambda 表达式作为一等公民,可以作为变量进行传递或者返回。
-
-以下脚本的输出为 3:
-
-```qlexpress
-add = (a, b) -> {
- return a + b;
-};
-add(1,2)
-```
-
-### 列表过滤和映射
-
-支持通过 filter, map 方法直接对列表类型进行函数式过滤和映射。
-底层通过在列表类型添加 [扩展函数](#扩展函数) 实现,注意和 [Stream Api](#stream-api) 中同名方法区分。
-相比 Stream Api,它可以直接对列表进行操作,返回值也直接就是列表,更加方便。
-
-```qlexpress
-l = ["a-111", "a-222", "b-333", "c-888"]
-// Output: ["111", "222"]
-l.filter(i -> i.startsWith("a-")).map(i -> i.split("-")[1]);
-```
-
-### 兼容 Java8 语法
-
-QLExpress 可以兼容 Java8 的常见语法。
-
-比如 [for-each循环](#for-each-循环), Stream 语法, 函数式接口等等。
-
-### Stream Api
-
-可以直接使用 Java 集合中的 stream api 对集合进行操作。
-
-因为此时的 stream api 都是来自 Java 中的方法,参考 [调用应用中的 Java 类](#调用应用中的-java-类) 打开安全选项,以下脚本才能正常执行。
-
-```qlexpress
-l = ["a-111", "a-222", "b-333", "c-888"]
-
-// Output: ["111", "222"]
-l.stream()
- .filter(i -> i.startsWith("a-"))
- .map(i -> i.split("-")[1])
- .collect(Collectors.toList())
-```
-
-### 函数式接口
-
-Java8 中引入了 Function, Consumer, Predicate 等函数式接口,QLExpress 中的 [Lambda表达式](#lambda表达式) 可以赋值给这些接口,
-或者作为接收这些接口的方法参数:
-
-```qlexpress
-Supplier s = () -> "test";
-// Output: test
-s.get()
-```
-
-# 附录一 QLExpress4性能提升
-
-[QLExpress4与3性能对比](https://www.yuque.com/xuanheng-ffjti/iunlps/pgfzw46zel2xfnie?singleDoc#%20%E3%80%8AQLExpress3%E4%B8%8E4%E6%80%A7%E8%83%BD%E5%AF%B9%E6%AF%94%E3%80%8B)
-
-总结:常见场景下,无编译缓存时,QLExpress4能比3有接近10倍性能提升;有编译缓存,也有一倍性能提升。
-
-# 附录二 开发者联系方式
-
- - email:qinyuan.dqy@alibaba-inc.com,yumin.pym@taobao.com,704643716@qq.com
- - wechat:
- - xuanheng: dqy932087612
- - binggou: pymbupt
- - linxiang: tkk33362
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index e9e9ac95..622a755b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.alibaba
QLExpress4
jar
- 4.0.0-beta.1
+ 4.0.0-beta.2
QLExpress
QLExpress is a powerful, lightweight, dynamic language for the Java platform aimed at improving
developers’ productivity in different business scenes.
diff --git a/src/main/antlr4/QLParser.g4 b/src/main/antlr4/QLParser.g4
index 1ccf3661..abfa900f 100644
--- a/src/main/antlr4/QLParser.g4
+++ b/src/main/antlr4/QLParser.g4
@@ -329,7 +329,7 @@ doubleQuoteStringLiteral
;
stringExpression
- : {interpolationMode == SCRIPT}? DyStrExprStart expression StrExpr_END
+ : {interpolationMode == SCRIPT}? DyStrExprStart expression RBRACE
| {interpolationMode == VARIABLE}? DyStrExprStart SelectorVariable_VANME RBRACE
;
diff --git a/src/main/antlr4/QLexer.g4 b/src/main/antlr4/QLexer.g4
index 39a98264..108f9639 100644
--- a/src/main/antlr4/QLexer.g4
+++ b/src/main/antlr4/QLexer.g4
@@ -1051,8 +1051,6 @@ SelectorVariable_RBRACE: RBRACE -> popMode, type(RBRACE);
mode StringExpression;
-StrExpr_END: '$}' -> popMode;
-
StrExpr_FOR: FOR -> type(FOR);
StrExpr_IF: IF -> type(IF);
StrExpr_ELSE: ELSE -> type(ELSE);
@@ -1094,8 +1092,8 @@ StrExpr_IntegerOrFloatingLiteral: IntegerOrFloatingLiteral -> type(IntegerOrFloa
StrExpr_LPAREN: LPAREN -> type(LPAREN);
StrExpr_RPAREN: RPAREN -> type(RPAREN);
-StrExpr_LBRACE: LBRACE -> type(LBRACE);
-StrExpr_RBRACE: RBRACE -> type(RBRACE);
+StrExpr_LBRACE: LBRACE -> pushMode(StringExpression), type(LBRACE);
+StrExpr_RBRACE: RBRACE -> popMode, type(RBRACE);
StrExpr_LBRACK: LBRACK -> type(LBRACK);
StrExpr_RBRACK: RBRACK -> type(RBRACK);
diff --git a/src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java b/src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java
index ebb012d9..eb9b75b0 100644
--- a/src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java
+++ b/src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java
@@ -32,6 +32,16 @@
*/
public class Express4RunnerTest {
+ @Test
+ public void cacheDocTest() {
+ // tag::cacheSwitch[]
+ Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
+ // open cache switch
+ express4Runner.execute("1+2", new HashMap<>(), QLOptions.builder()
+ .cache(true).build());
+ // end::cacheSwitch[]
+ }
+
@Test
public void docQuickStartTest() {
// tag::firstQl[]
@@ -47,6 +57,7 @@ public void docQuickStartTest() {
@Test
public void docAddFunctionAndOperatorTest() {
+ // tag::addFunctionAndOperator[]
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
// custom function
express4Runner.addVarArgsFunction("join", params ->
@@ -58,10 +69,12 @@ public void docAddFunctionAndOperatorTest() {
express4Runner.addOperatorBiFunction("join", (left, right) -> left + "," + right);
Object resultOperator = express4Runner.execute("1 join 2 join 3", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS);
assertEquals("1,2,3", resultOperator);
+ // end::addFunctionAndOperator[]
}
@Test
public void docImportJavaTest() {
+ // tag::importJavaCls[]
Express4Runner express4Runner = new Express4Runner(InitOptions.builder()
// open security strategy, which allows access to all Java classes within the application.
.securityStrategy(QLSecurityStrategy.open())
@@ -71,10 +84,12 @@ public void docImportJavaTest() {
Object result = express4Runner.execute("import com.alibaba.qlexpress4.QLImportTester;" +
"QLImportTester.add(1,2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS);
Assert.assertEquals(3, result);
+ // end::importJavaCls[]
}
@Test
public void docDefaultImportJavaTest() {
+ // tag::defaultImport[]
Express4Runner express4Runner = new Express4Runner(InitOptions.builder()
.addDefaultImport(
Collections.singletonList(ImportManager.importCls("com.alibaba.qlexpress4.QLImportTester"))
@@ -84,6 +99,7 @@ public void docDefaultImportJavaTest() {
);
Object result = express4Runner.execute("QLImportTester.add(1,2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS);
Assert.assertEquals(3, result);
+ // end::defaultImport[]
}
@Test
@@ -100,17 +116,25 @@ public void docTryCatchTest() {
@Test
public void docPreciseTest() {
+ // tag::bigDecimalForPrecise[]
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
Object result = express4Runner.execute("0.1", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS);
assertTrue(result instanceof BigDecimal);
+ // end::bigDecimalForPrecise[]
+
+ // tag::preciseComparisonWithJava[]
assertNotEquals(0.3, 0.1 + 0.2, 0.0);
assertTrue((Boolean) express4Runner.execute("0.3==0.1+0.2", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS));
+ // end::preciseComparisonWithJava[]
+ // tag::preciseSwitch[]
Map context = new HashMap<>();
context.put("a", 0.1);
context.put("b", 0.2);
assertFalse((Boolean) express4Runner.execute("0.3==a+b", context, QLOptions.DEFAULT_OPTIONS));
+ // open precise switch
assertTrue((Boolean) express4Runner.execute("0.3==a+b", context, QLOptions.builder().precise(true).build()));
+ // end::preciseSwitch[]
}
@@ -231,6 +255,7 @@ public void invalidOperatorTest() {
@Test
public void extensionFunctionTest() {
+ // tag::extensionFunction[]
ExtensionFunction helloFunction = new ExtensionFunction() {
@Override
public Class>[] getParameterTypes() {
@@ -258,6 +283,7 @@ public Object invoke(Object obj, Object[] args) throws InvocationTargetException
.build());
Object result = express4Runner.execute("'jack'.hello()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS);
assertEquals("Hello,jack", result);
+ // end::extensionFunction[]
}
@Test
@@ -587,6 +613,7 @@ public void qlAliasTest() {
@Test
public void qlAliasDocTest() {
+ // tag::qlAlias[]
Order order = new Order();
order.setOrderNum("OR123455");
order.setAmount(100);
@@ -601,10 +628,12 @@ public void qlAliasDocTest() {
Number result = (Number) express4Runner.executeWithAliasObjects("用户.是vip? 订单.金额 * 0.8 : 订单.金额",
QLOptions.DEFAULT_OPTIONS, order, user);
assertEquals(80, result.intValue());
+ // end::qlAlias[]
}
@Test
- public void customFunctionDocTest() {
+ public void customComplexFunctionDocTest() {
+ // tag::customComplexFunction[]
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
express4Runner.addFunction("hello", new HelloFunction());
String resultJack = (String) express4Runner.execute("hello()", Collections.emptyMap(), QLOptions.builder()
@@ -613,6 +642,7 @@ public void customFunctionDocTest() {
String resultLucy = (String) express4Runner.execute("hello()", Collections.emptyMap(), QLOptions.builder()
.attachments(Collections.singletonMap("tenant", "lucy")).build());
assertEquals("hello,lucy", resultLucy);
+ // end::customComplexFunction[]
}
private void assertResultEquals(Express4Runner express4Runner, String script, Object expect) {
diff --git a/src/test/java/com/alibaba/qlexpress4/TestSuiteRunner.java b/src/test/java/com/alibaba/qlexpress4/TestSuiteRunner.java
index c63384a9..d79d9a7b 100644
--- a/src/test/java/com/alibaba/qlexpress4/TestSuiteRunner.java
+++ b/src/test/java/com/alibaba/qlexpress4/TestSuiteRunner.java
@@ -56,7 +56,7 @@ public void suiteTest() throws URISyntaxException, IOException {
@Test
public void featureDebug() throws URISyntaxException, IOException {
- Path filePath = getTestSuiteRoot().resolve("java/stream/java_stream.ql");
+ Path filePath = getTestSuiteRoot().resolve("independent/doc/dynamic_typing.ql");
handleFile(filePath, filePath.toString(), true);
}
diff --git a/src/test/java/com/alibaba/qlexpress4/aparser/SyntaxTreeFactoryTest.java b/src/test/java/com/alibaba/qlexpress4/aparser/SyntaxTreeFactoryTest.java
index 94b3a57d..cf7824ab 100644
--- a/src/test/java/com/alibaba/qlexpress4/aparser/SyntaxTreeFactoryTest.java
+++ b/src/test/java/com/alibaba/qlexpress4/aparser/SyntaxTreeFactoryTest.java
@@ -431,7 +431,7 @@ public void selectorTest() {
@Test
public void doubleQuoteStringScriptTest() {
- List instructs = getScriptInstructions("\"a ${v-1$}\"", InterpolationMode.SCRIPT);
+ List instructs = getScriptInstructions("\"a ${v-1}\"", InterpolationMode.SCRIPT);
ConstInstruction i0 = (ConstInstruction) instructs.get(0);
assertEquals("a ", i0.getConstObj());
LoadInstruction i1 = (LoadInstruction) instructs.get(1);
@@ -446,7 +446,7 @@ public void doubleQuoteStringScriptTest() {
@Test
public void doubleQuoteStringScriptTest2() {
- QLParser.ProgramContext programContext = SyntaxTreeFactory.buildTree("\"Hello ${a$} ccc\"",
+ QLParser.ProgramContext programContext = SyntaxTreeFactory.buildTree("\"Hello ${a} ccc\"",
new MockOpM(), true, false, System.out::println, InterpolationMode.SCRIPT);
}
diff --git a/src/test/resources/testsuite/independent/doc/convenient_syntax_elements.ql b/src/test/resources/testsuite/independent/doc/convenient_syntax_elements.ql
new file mode 100644
index 00000000..4ef808f3
--- /dev/null
+++ b/src/test/resources/testsuite/independent/doc/convenient_syntax_elements.ql
@@ -0,0 +1,16 @@
+// list
+l = [1,2,3]
+assert(l[0]==1)
+// Underlying data type of list is ArrayList in Java
+assert(l instanceof ArrayList)
+// map
+m = {
+ "aa": 10,
+ "bb": {
+ "cc": "cc1",
+ "dd": "dd1"
+ }
+}
+assert(m['aa']==10)
+// Underlying data type of map is ArrayList in Java
+assert(m instanceof LinkedHashMap)
\ No newline at end of file
diff --git a/src/test/resources/testsuite/independent/doc/dynamic_string.ql b/src/test/resources/testsuite/independent/doc/dynamic_string.ql
new file mode 100644
index 00000000..b0119940
--- /dev/null
+++ b/src/test/resources/testsuite/independent/doc/dynamic_string.ql
@@ -0,0 +1,9 @@
+a = 123
+assert("hello,${a-1}" == "hello,122")
+
+b = "test"
+assert("m xx ${
+ if (b like 't%') {
+ 'YYY'
+ }
+}" == "m xx YYY")
\ No newline at end of file
diff --git a/src/test/resources/testsuite/independent/doc/dynamic_typing.ql b/src/test/resources/testsuite/independent/doc/dynamic_typing.ql
new file mode 100644
index 00000000..7458a82a
--- /dev/null
+++ b/src/test/resources/testsuite/independent/doc/dynamic_typing.ql
@@ -0,0 +1,8 @@
+// Dynamic Typeing
+a = 1;
+a = "1";
+// Static Typing
+int b = 2;
+// throw QLException with error code INCOMPATIBLE_ASSIGNMENT_TYPE when assign with incompatible type String
+assertErrorCode(() -> b = "1", "INCOMPATIBLE_ASSIGNMENT_TYPE")
+
diff --git a/src/test/resources/testsuite/independent/doc/for.ql b/src/test/resources/testsuite/independent/doc/for.ql
new file mode 100644
index 00000000..92404894
--- /dev/null
+++ b/src/test/resources/testsuite/independent/doc/for.ql
@@ -0,0 +1,5 @@
+l = [];
+for (int i = 3; i < 6; i++) {
+ l.add(i);
+}
+assert(l==[3,4,5])
\ No newline at end of file
diff --git a/src/test/resources/testsuite/independent/doc/for_each.ql b/src/test/resources/testsuite/independent/doc/for_each.ql
new file mode 100644
index 00000000..9065e5e6
--- /dev/null
+++ b/src/test/resources/testsuite/independent/doc/for_each.ql
@@ -0,0 +1,8 @@
+sum = 0;
+for (i: [0,1,2,3,4]) {
+ if (i == 2) {
+ continue;
+ }
+ sum += i;
+}
+assert(sum==8)
\ No newline at end of file
diff --git a/src/test/resources/testsuite/independent/doc/function.ql b/src/test/resources/testsuite/independent/doc/function.ql
new file mode 100644
index 00000000..6744785e
--- /dev/null
+++ b/src/test/resources/testsuite/independent/doc/function.ql
@@ -0,0 +1,4 @@
+function sub(a, b) {
+ return a-b;
+}
+assert(sub(3,1)==2)
\ No newline at end of file
diff --git a/src/test/resources/testsuite/independent/doc/if.ql b/src/test/resources/testsuite/independent/doc/if.ql
new file mode 100644
index 00000000..64543ac3
--- /dev/null
+++ b/src/test/resources/testsuite/independent/doc/if.ql
@@ -0,0 +1,8 @@
+a = 11;
+assert(if (a >= 0 && a < 5) {
+ true
+} else if (a >= 5 && a < 10) {
+ false
+} else if (a >= 10 && a < 15) {
+ true
+} == true)
\ No newline at end of file
diff --git a/src/test/resources/testsuite/independent/doc/if_as_expr.ql b/src/test/resources/testsuite/independent/doc/if_as_expr.ql
new file mode 100644
index 00000000..424dbcc0
--- /dev/null
+++ b/src/test/resources/testsuite/independent/doc/if_as_expr.ql
@@ -0,0 +1,5 @@
+assert(if (11 == 11) {
+ 10
+} else {
+ 20 + 2
+} + 1 == 11)
\ No newline at end of file
diff --git a/src/test/resources/testsuite/independent/doc/lambda.ql b/src/test/resources/testsuite/independent/doc/lambda.ql
new file mode 100644
index 00000000..07c162a9
--- /dev/null
+++ b/src/test/resources/testsuite/independent/doc/lambda.ql
@@ -0,0 +1,4 @@
+add = (a, b) -> {
+ return a + b;
+}
+assert(add(1,2)==3)
\ No newline at end of file
diff --git a/src/test/resources/testsuite/independent/doc/list_map_filter.ql b/src/test/resources/testsuite/independent/doc/list_map_filter.ql
new file mode 100644
index 00000000..c8d86a45
--- /dev/null
+++ b/src/test/resources/testsuite/independent/doc/list_map_filter.ql
@@ -0,0 +1,3 @@
+l = ["a-111", "a-222", "b-333", "c-888"]
+assert(l.filter(i -> i.startsWith("a-"))
+ .map(i -> i.split("-")[1]) == ["111", "222"])
\ No newline at end of file
diff --git a/src/test/resources/testsuite/independent/doc/try_catch.ql b/src/test/resources/testsuite/independent/doc/try_catch.ql
new file mode 100644
index 00000000..cb836db1
--- /dev/null
+++ b/src/test/resources/testsuite/independent/doc/try_catch.ql
@@ -0,0 +1,6 @@
+assert(try {
+ 100 + 1/0
+} catch(e) {
+ // Throw a zero-division exception
+ 11
+} == 11)
\ No newline at end of file
diff --git a/src/test/resources/testsuite/independent/doc/try_catch_as_expr.ql b/src/test/resources/testsuite/independent/doc/try_catch_as_expr.ql
new file mode 100644
index 00000000..4a9ccfa3
--- /dev/null
+++ b/src/test/resources/testsuite/independent/doc/try_catch_as_expr.ql
@@ -0,0 +1,6 @@
+assert(1 + try {
+ 100 + 1/0
+} catch(e) {
+ // Throw a zero-division exception
+ 11
+} == 12)
\ No newline at end of file
diff --git a/src/test/resources/testsuite/independent/doc/while.ql b/src/test/resources/testsuite/independent/doc/while.ql
new file mode 100644
index 00000000..8b2f7327
--- /dev/null
+++ b/src/test/resources/testsuite/independent/doc/while.ql
@@ -0,0 +1,7 @@
+i = 0;
+while (i < 5) {
+ if (++i == 2) {
+ break;
+ }
+}
+assert(i==2)
\ No newline at end of file
diff --git a/src/test/resources/testsuite/independent/string/interpolation.ql b/src/test/resources/testsuite/independent/string/interpolation.ql
index 950f401f..acd4e127 100644
--- a/src/test/resources/testsuite/independent/string/interpolation.ql
+++ b/src/test/resources/testsuite/independent/string/interpolation.ql
@@ -1,37 +1,37 @@
a = 123;
b = "test"
-assert("Hello ${a$} ${b $} ccc" == "Hello 123 test ccc");
+assert("Hello ${a} ${b } ccc" == "Hello 123 test ccc");
// $ escape
assert("Hello \${a bb cc" == 'Hello ${a bb cc')
// selector variable
assert(${a} == 123)
-assert("${a-1$}" == "122")
+assert("${a-1}" == "122")
assert("m xx ${
if (b like 't%') {
'YYY'
}
-$}" == "m xx YYY")
+}" == "m xx YYY")
assert("m xx ${
if (b like 't%') {
"YYY"
}
-$}" == "m xx YYY")
+}" == "m xx YYY")
// nest interpolation
assert("m xx ${
if (b like 't%') {
- "YY${b$}Y"
+ "YY${b}Y"
}
-$}" == "m xx YYtestY")
+}" == "m xx YYtestY")
assert("m xx ${
if (b like 'mm%') {
'YYY'
}
-$}" == "m xx null")
+}" == "m xx null")