first
This commit is contained in:
28
.github/workflows/maven-publish.yml
vendored
Normal file
28
.github/workflows/maven-publish.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Maven Central Repo Deployment
|
||||
on:
|
||||
release:
|
||||
types: [released, prereleased]
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Git Repo
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up Maven Central Repo
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 8
|
||||
server-id: ossrh
|
||||
server-username: 'OSSRH_USER'
|
||||
server-password: 'OSSRH_PASSWORD'
|
||||
gpg-passphrase: 'MAVEN_GPG_PASSPHRASE'
|
||||
gpg-private-key: ${{ secrets.GPG_SECRET }}
|
||||
- name: debug settings.xml
|
||||
run: cat /home/runner/.m2/settings.xml
|
||||
- name: Publish to Maven Central Repo
|
||||
run: mvn clean deploy --batch-mode --activate-profiles deploy
|
||||
env:
|
||||
OSSRH_USER: ${{ secrets.OSSRH_TOKEN_USER }}
|
||||
OSSRH_PASSWORD: ${{ secrets.OSSRH_TOKEN_PASSWORD }}
|
||||
MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSWORD }}
|
||||
42
.github/workflows/reduce-adoc.yml
vendored
Normal file
42
.github/workflows/reduce-adoc.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: Reduce Adoc
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '**/*-source.adoc'
|
||||
branches: ['**']
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
reduce:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.2'
|
||||
|
||||
- name: Install asciidoctor-reducer
|
||||
run: gem install --no-document asciidoctor-reducer
|
||||
|
||||
- name: Reduce all *-source.adoc files
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mapfile -d '' -t sources < <(find . -type f -name '*-source.adoc' -print0)
|
||||
for src in "${sources[@]}"; do
|
||||
out="${src%-source.adoc}.adoc"
|
||||
mkdir -p "$(dirname "$out")"
|
||||
echo "Reducing $src -> $out"
|
||||
asciidoctor-reducer --preserve-conditionals -o "$out" "$src"
|
||||
done
|
||||
|
||||
- name: Commit reduced files
|
||||
uses: EndBug/add-and-commit@v9
|
||||
20
.github/workflows/unittest.yml
vendored
Normal file
20
.github/workflows/unittest.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: Java Unit Test with Maven
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'temurin'
|
||||
- name: Run tests with Maven
|
||||
run: mvn -B clean test --file pom.xml
|
||||
- name: Upload results to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Java template
|
||||
*.class
|
||||
|
||||
target/*
|
||||
*.iml
|
||||
.idea/
|
||||
.spec/
|
||||
openspec/
|
||||
AGENTS.md
|
||||
gen/
|
||||
|
||||
.vscode/
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
!src/test/resources/test-plugins/*.jar
|
||||
dependency-reduced-pom.xml
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
test.log
|
||||
test-push.sh
|
||||
|
||||
.DS_Store
|
||||
|
||||
src/main/antlr4/.antlr
|
||||
201
LICENSE
Normal file
201
LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
1108
README-EN-source.adoc
Normal file
1108
README-EN-source.adoc
Normal file
File diff suppressed because it is too large
Load Diff
1893
README-EN.adoc
Normal file
1893
README-EN.adoc
Normal file
File diff suppressed because it is too large
Load Diff
1109
README-source.adoc
Normal file
1109
README-source.adoc
Normal file
File diff suppressed because it is too large
Load Diff
1894
README.adoc
Normal file
1894
README.adoc
Normal file
File diff suppressed because it is too large
Load Diff
152
docs/custom-item-en-source.adoc
Normal file
152
docs/custom-item-en-source.adoc
Normal file
@@ -0,0 +1,152 @@
|
||||
:toc:
|
||||
|
||||
= Custom Syntax Elements
|
||||
|
||||
This section systematically outlines the interfaces and capabilities in QLExpress4 for customizing syntax elements.
|
||||
|
||||
== Overview
|
||||
|
||||
* Custom functions
|
||||
** Implement the CustomFunction interface
|
||||
** Use Java functional interfaces
|
||||
** Register via annotation scanning
|
||||
** Add via QLExpress script
|
||||
** Implement QLFunctionalVarargs
|
||||
* Custom operators
|
||||
** Implement CustomBinaryOperator
|
||||
** Replace built-in operators
|
||||
** Use Java functional interfaces
|
||||
** Add aliases
|
||||
** Implement QLFunctionalVarargs
|
||||
* Extension functions
|
||||
** Extend ExtensionFunction
|
||||
** Implement QLFunctionalVarargs
|
||||
* Aliases for operators and functions
|
||||
|
||||
== Custom Functions
|
||||
|
||||
=== Implement the CustomFunction interface
|
||||
|
||||
[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]
|
||||
----
|
||||
|
||||
=== Use Java functional interfaces
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addFunctionWithJavaFunctional]
|
||||
----
|
||||
|
||||
=== Register via annotations
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=annoObj]
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=addFunctionByAnnotationObject]
|
||||
----
|
||||
|
||||
=== Add via QLExpress script
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=joinFunctionVarargsObj]
|
||||
----
|
||||
|
||||
=== Implement QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addFunctionByVarargs]
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addFunctionByVarargs]
|
||||
----
|
||||
|
||||
== Custom Operators
|
||||
|
||||
=== Implement CustomBinaryOperator and set precedence
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addOperatorWithPrecedence]
|
||||
----
|
||||
|
||||
=== Replace built-in operators
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=replaceDefaultOperator]
|
||||
----
|
||||
|
||||
=== Use Java functional interfaces
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addOperatorBiFunction]
|
||||
----
|
||||
|
||||
=== Implement QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addOperatorByVarargs]
|
||||
----
|
||||
|
||||
== Extension Functions
|
||||
|
||||
=== Extend ExtensionFunction
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=defineExtensionFunctionByExtensionFunction]
|
||||
----
|
||||
|
||||
=== Implement QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=defineExtensionFunctionByQLFunctionalVarargs]
|
||||
----
|
||||
|
||||
== QLFunctionalVarargs: One object defines three kinds of operations
|
||||
|
||||
The same QLFunctionalVarargs object can serve as the implementation of a function, an operator, and an extension function at the same time, making it easy to reuse unified semantics and implementation across multiple places. This capability comes from the interface’s varargs design; see the example and interface definition below. For background discussion, refer to link:https://github.com/alibaba/QLExpress/issues/407[issue407]:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=qlfunctionalvarargsAllInOne]
|
||||
----
|
||||
|
||||
=== Interface definition
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/main/java/com/alibaba/qlexpress4/api/QLFunctionalVarargs.java[]
|
||||
----
|
||||
|
||||
== Aliases for operators and functions (keyword aliases also supported)
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=addAlias]
|
||||
----
|
||||
|
||||
== Notes and recommendations
|
||||
|
||||
- Under the QLFunctionalVarargs calling convention, when invoking an extension function the first actual argument is the receiver object, followed by the call arguments; functions and operators do not include a receiver.
|
||||
- Set the precedence of custom operators according to the intended expression semantics to avoid confusion with existing rules.
|
||||
- Annotation-based registration only processes public methods, and duplicate names will fail to register; the batch registration result contains both success and failure lists.
|
||||
|
||||
334
docs/custom-item-en.adoc
Normal file
334
docs/custom-item-en.adoc
Normal file
@@ -0,0 +1,334 @@
|
||||
:toc:
|
||||
|
||||
= Custom Syntax Elements
|
||||
|
||||
This section systematically outlines the interfaces and capabilities in QLExpress4 for customizing syntax elements.
|
||||
|
||||
== Overview
|
||||
|
||||
* Custom functions
|
||||
** Implement the CustomFunction interface
|
||||
** Use Java functional interfaces
|
||||
** Register via annotation scanning
|
||||
** Add via QLExpress script
|
||||
** Implement QLFunctionalVarargs
|
||||
* Custom operators
|
||||
** Implement CustomBinaryOperator
|
||||
** Replace built-in operators
|
||||
** Use Java functional interfaces
|
||||
** Add aliases
|
||||
** Implement QLFunctionalVarargs
|
||||
* Extension functions
|
||||
** Extend ExtensionFunction
|
||||
** Implement QLFunctionalVarargs
|
||||
* Aliases for operators and functions
|
||||
|
||||
== Custom Functions
|
||||
|
||||
=== Implement the CustomFunction interface
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
package com.alibaba.qlexpress4.test.function;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
express4Runner.addFunction("hello", new HelloFunction());
|
||||
String resultJack = (String)express4Runner.execute("hello()",
|
||||
Collections.emptyMap(),
|
||||
// Additional information(tenant for example) can be brought into the custom function from outside via attachments
|
||||
QLOptions.builder().attachments(Collections.singletonMap("tenant", "jack")).build()).getResult();
|
||||
assertEquals("hello,jack", resultJack);
|
||||
String resultLucy =
|
||||
(String)express4Runner
|
||||
.execute("hello()",
|
||||
Collections.emptyMap(),
|
||||
QLOptions.builder().attachments(Collections.singletonMap("tenant", "lucy")).build())
|
||||
.getResult();
|
||||
assertEquals("hello,lucy", resultLucy);
|
||||
----
|
||||
|
||||
=== Use Java functional interfaces
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
// Function<T,R>
|
||||
runner.addFunction("inc", (Function<Integer, Integer>)x -> x + 1);
|
||||
// Predicate<T>
|
||||
runner.addFunction("isPos", (Predicate<Integer>)x -> x > 0);
|
||||
// Runnable
|
||||
runner.addFunction("notify", () -> {
|
||||
});
|
||||
// Consumer<T>
|
||||
runner.addFunction("print", (Consumer<Object>)System.out::println);
|
||||
|
||||
Object r1 = runner.execute("inc(1)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
Object r2 = runner.execute("isPos(1)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(2, r1);
|
||||
assertEquals(true, r2);
|
||||
----
|
||||
|
||||
=== Register via annotations
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
public static class MyFunctionUtil {
|
||||
@QLFunction({"myAdd", "iAdd"})
|
||||
public int add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
@QLFunction("arr3")
|
||||
public static int[] array3(int a, int b, int c) {
|
||||
return new int[] {a, b, c};
|
||||
}
|
||||
|
||||
@QLFunction("concat")
|
||||
public String concat(String a, String b) {
|
||||
return a + b;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
BatchAddFunctionResult addResult = express4Runner.addObjFunction(new MyFunctionUtil());
|
||||
assertEquals(4, addResult.getSucc().size());
|
||||
Object result =
|
||||
express4Runner.execute("myAdd(1,2) + iAdd(5,6)", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(14, result);
|
||||
express4Runner.addStaticFunction(MyFunctionUtil.class);
|
||||
Object result1 =
|
||||
express4Runner.execute("arr3(5,9,10)[2]", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(10, result1);
|
||||
|
||||
Object result2 =
|
||||
express4Runner.execute("concat('aa', null)", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("aanull", result2);
|
||||
----
|
||||
|
||||
=== Add via QLExpress script
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
public static class JoinFunction implements QLFunctionalVarargs {
|
||||
@Override
|
||||
public Object call(Object... params) {
|
||||
return Arrays.stream(params).map(Object::toString).collect(Collectors.joining(","));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
=== Implement QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
express4Runner.addVarArgsFunction("join", new JoinFunction());
|
||||
Object resultFunction =
|
||||
express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("1,2,3", resultFunction);
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
express4Runner.addVarArgsFunction("join", new JoinFunction());
|
||||
Object resultFunction =
|
||||
express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("1,2,3", resultFunction);
|
||||
----
|
||||
|
||||
== Custom Operators
|
||||
|
||||
=== Implement CustomBinaryOperator and set precedence
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
runner.addOperator("?><", (left, right) -> left.get().toString() + right.get().toString(), QLPrecedences.ADD);
|
||||
Object r = runner.execute("1 ?>< 2 * 3", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
// precedence set to ADD, so multiply first, then custom operator: "1" + "6" => "16"
|
||||
assertEquals("16", r);
|
||||
----
|
||||
|
||||
=== Replace built-in operators
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
boolean ok = runner.replaceDefaultOperator("+",
|
||||
(left, right) -> Double.parseDouble(left.get().toString()) + Double.parseDouble(right.get().toString()));
|
||||
assertTrue(ok);
|
||||
Object r = runner.execute("'1.2' + '2.3'", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(3.5d, r);
|
||||
----
|
||||
|
||||
=== Use Java functional interfaces
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
express4Runner.addOperatorBiFunction("join", (left, right) -> left + "," + right);
|
||||
Object resultOperator =
|
||||
express4Runner.execute("1 join 2 join 3", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("1,2,3", resultOperator);
|
||||
----
|
||||
|
||||
=== Implement QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
runner.addOperator("join", params -> params[0] + "," + params[1]);
|
||||
Object r = runner.execute("1 join 2", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("1,2", r);
|
||||
----
|
||||
|
||||
== Extension Functions
|
||||
|
||||
=== Extend ExtensionFunction
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
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) {
|
||||
String originStr = (String)obj;
|
||||
return "Hello," + originStr;
|
||||
}
|
||||
};
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
express4Runner.addExtendFunction(helloFunction);
|
||||
Object result =
|
||||
express4Runner.execute("'jack'.hello()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("Hello,jack", result);
|
||||
----
|
||||
|
||||
=== Implement QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
// simpler way to define extension function
|
||||
express4Runner.addExtendFunction("add",
|
||||
Number.class,
|
||||
params -> ((Number)params[0]).intValue() + ((Number)params[1]).intValue());
|
||||
QLResult resultAdd = express4Runner.execute("1.add(2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS);
|
||||
assertEquals(3, resultAdd.getResult());
|
||||
----
|
||||
|
||||
== QLFunctionalVarargs: One object defines three kinds of operations
|
||||
|
||||
The same QLFunctionalVarargs object can serve as the implementation of a function, an operator, and an extension function at the same time, making it easy to reuse unified semantics and implementation across multiple places. This capability comes from the interface’s varargs design; see the example and interface definition below. For background discussion, refer to link:https://github.com/alibaba/QLExpress/issues/407[issue407]:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
QLFunctionalVarargs allInOne = params -> {
|
||||
// sum numbers no matter how many args
|
||||
double sum = 0d;
|
||||
for (Object p : params) {
|
||||
if (p instanceof Number) {
|
||||
sum += ((Number)p).doubleValue();
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
};
|
||||
|
||||
// as function
|
||||
runner.addVarArgsFunction("sumAll", allInOne);
|
||||
// as operator
|
||||
runner.addOperator("+&", allInOne);
|
||||
// as extension function: first arg is the receiver, followed by call arguments
|
||||
runner.addExtendFunction("plusAll", Number.class, allInOne);
|
||||
|
||||
Map<String, Object> ctx = new HashMap<>();
|
||||
Object rf = runner.execute("sumAll(1,2,3)", ctx, QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
Object ro = runner.execute("1 +& 4", ctx, QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
Object re = runner.execute("1.plusAll(5)", ctx, QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(6d, rf);
|
||||
assertEquals(5d, ro);
|
||||
assertEquals(6d, re);
|
||||
----
|
||||
|
||||
=== Interface definition
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
package com.alibaba.qlexpress4.api;
|
||||
|
||||
/**
|
||||
* Author: TaoKan
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface QLFunctionalVarargs {
|
||||
Object call(Object... params);
|
||||
}
|
||||
----
|
||||
|
||||
== Aliases for operators and functions (keyword aliases also supported)
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
// add custom function zero
|
||||
express4Runner.addFunction("zero", (String ignore) -> 0);
|
||||
|
||||
// keyword alias
|
||||
assertTrue(express4Runner.addAlias("如果", "if"));
|
||||
assertTrue(express4Runner.addAlias("则", "then"));
|
||||
assertTrue(express4Runner.addAlias("否则", "else"));
|
||||
assertTrue(express4Runner.addAlias("返回", "return"));
|
||||
// operator alias
|
||||
assertTrue(express4Runner.addAlias("大于", ">"));
|
||||
// function alias
|
||||
assertTrue(express4Runner.addAlias("零", "zero"));
|
||||
|
||||
Map<String, Object> context = new HashMap<>();
|
||||
context.put("语文", 90);
|
||||
context.put("数学", 90);
|
||||
context.put("英语", 90);
|
||||
|
||||
Object result = express4Runner
|
||||
.execute("如果 (语文 + 数学 + 英语 大于 270) 则 {返回 1;} 否则 {返回 零();}", context, QLOptions.DEFAULT_OPTIONS)
|
||||
.getResult();
|
||||
assertEquals(0, result);
|
||||
----
|
||||
|
||||
== Notes and recommendations
|
||||
|
||||
- Under the QLFunctionalVarargs calling convention, when invoking an extension function the first actual argument is the receiver object, followed by the call arguments; functions and operators do not include a receiver.
|
||||
- Set the precedence of custom operators according to the intended expression semantics to avoid confusion with existing rules.
|
||||
- Annotation-based registration only processes public methods, and duplicate names will fail to register; the batch registration result contains both success and failure lists.
|
||||
152
docs/custom-item-source.adoc
Normal file
152
docs/custom-item-source.adoc
Normal file
@@ -0,0 +1,152 @@
|
||||
:toc:
|
||||
|
||||
= 自定义语法元素
|
||||
|
||||
本节系统梳理在 QLExpress4 在自定义语法元素方面的接口和能力。
|
||||
|
||||
== 总览
|
||||
|
||||
* 自定义函数
|
||||
** 实现CustomFunction接口
|
||||
** 使用 Java 函数式接口
|
||||
** 通过注解扫描注册
|
||||
** 通过QLExpress脚本添加
|
||||
** 实现QLFunctionalVarargs
|
||||
* 自定义操作符
|
||||
** 实现CustomBinaryOperator
|
||||
** 替换内置操作符
|
||||
** 使用 Java 函数式接口
|
||||
** 添加别名
|
||||
** 实现QLFunctionalVarargs
|
||||
* 扩展函数
|
||||
** 继承ExtensionFunction
|
||||
** 实现QLFunctionalVarargs
|
||||
* 操作符与函数别名
|
||||
|
||||
== 自定义函数
|
||||
|
||||
=== 实现CustomFunction接口
|
||||
|
||||
[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]
|
||||
----
|
||||
|
||||
=== 使用 Java 函数式接口
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addFunctionWithJavaFunctional]
|
||||
----
|
||||
|
||||
=== 注解方式注册
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=annoObj]
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=addFunctionByAnnotationObject]
|
||||
----
|
||||
|
||||
=== 通过QLExpress脚本添加
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=joinFunctionVarargsObj]
|
||||
----
|
||||
|
||||
=== 实现QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addFunctionByVarargs]
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addFunctionByVarargs]
|
||||
----
|
||||
|
||||
== 自定义操作符
|
||||
|
||||
=== 实现CustomBinaryOperator并设置优先级
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addOperatorWithPrecedence]
|
||||
----
|
||||
|
||||
=== 替换内置操作符
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=replaceDefaultOperator]
|
||||
----
|
||||
|
||||
=== 使用 Java 函数式接口
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addOperatorBiFunction]
|
||||
----
|
||||
|
||||
=== 实现QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addOperatorByVarargs]
|
||||
----
|
||||
|
||||
== 扩展函数
|
||||
|
||||
=== 继承ExtensionFunction
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=defineExtensionFunctionByExtensionFunction]
|
||||
----
|
||||
|
||||
=== 实现QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=defineExtensionFunctionByQLFunctionalVarargs]
|
||||
----
|
||||
|
||||
== QLFunctionalVarargs:一个对象同时定义三类操作
|
||||
|
||||
同一个QLFunctionalVarargs对象可同时用作函数、操作符与扩展函数的实现,便于在多处复用统一的语义与实现。该能力来源于接口的可变参数设计,详见下方示例与接口定义。背景讨论参考 link:https://github.com/alibaba/QLExpress/issues/407[issue407]:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=qlfunctionalvarargsAllInOne]
|
||||
----
|
||||
|
||||
=== 接口定义
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/main/java/com/alibaba/qlexpress4/api/QLFunctionalVarargs.java[]
|
||||
----
|
||||
|
||||
== 操作符与函数别名(亦支持关键字别名)
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=addAlias]
|
||||
----
|
||||
|
||||
== 说明与建议
|
||||
|
||||
- QLFunctionalVarargs 的定义模式下,扩展函数调用时实参列表首位是接收者对象,其后为调用参数;函数与操作符不含接收者。
|
||||
- 自定义操作符的优先级需根据表达式期望进行设置,避免与已有运算规则产生混淆。
|
||||
- 注解方式注册仅会处理公开方法,且重复名称将注册失败;批量注册返回结果中包含成功与失败清单。
|
||||
|
||||
334
docs/custom-item.adoc
Normal file
334
docs/custom-item.adoc
Normal file
@@ -0,0 +1,334 @@
|
||||
:toc:
|
||||
|
||||
= 自定义语法元素
|
||||
|
||||
本节系统梳理在 QLExpress4 在自定义语法元素方面的接口和能力。
|
||||
|
||||
== 总览
|
||||
|
||||
* 自定义函数
|
||||
** 实现CustomFunction接口
|
||||
** 使用 Java 函数式接口
|
||||
** 通过注解扫描注册
|
||||
** 通过QLExpress脚本添加
|
||||
** 实现QLFunctionalVarargs
|
||||
* 自定义操作符
|
||||
** 实现CustomBinaryOperator
|
||||
** 替换内置操作符
|
||||
** 使用 Java 函数式接口
|
||||
** 添加别名
|
||||
** 实现QLFunctionalVarargs
|
||||
* 扩展函数
|
||||
** 继承ExtensionFunction
|
||||
** 实现QLFunctionalVarargs
|
||||
* 操作符与函数别名
|
||||
|
||||
== 自定义函数
|
||||
|
||||
=== 实现CustomFunction接口
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
package com.alibaba.qlexpress4.test.function;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
express4Runner.addFunction("hello", new HelloFunction());
|
||||
String resultJack = (String)express4Runner.execute("hello()",
|
||||
Collections.emptyMap(),
|
||||
// Additional information(tenant for example) can be brought into the custom function from outside via attachments
|
||||
QLOptions.builder().attachments(Collections.singletonMap("tenant", "jack")).build()).getResult();
|
||||
assertEquals("hello,jack", resultJack);
|
||||
String resultLucy =
|
||||
(String)express4Runner
|
||||
.execute("hello()",
|
||||
Collections.emptyMap(),
|
||||
QLOptions.builder().attachments(Collections.singletonMap("tenant", "lucy")).build())
|
||||
.getResult();
|
||||
assertEquals("hello,lucy", resultLucy);
|
||||
----
|
||||
|
||||
=== 使用 Java 函数式接口
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
// Function<T,R>
|
||||
runner.addFunction("inc", (Function<Integer, Integer>)x -> x + 1);
|
||||
// Predicate<T>
|
||||
runner.addFunction("isPos", (Predicate<Integer>)x -> x > 0);
|
||||
// Runnable
|
||||
runner.addFunction("notify", () -> {
|
||||
});
|
||||
// Consumer<T>
|
||||
runner.addFunction("print", (Consumer<Object>)System.out::println);
|
||||
|
||||
Object r1 = runner.execute("inc(1)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
Object r2 = runner.execute("isPos(1)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(2, r1);
|
||||
assertEquals(true, r2);
|
||||
----
|
||||
|
||||
=== 注解方式注册
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
public static class MyFunctionUtil {
|
||||
@QLFunction({"myAdd", "iAdd"})
|
||||
public int add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
@QLFunction("arr3")
|
||||
public static int[] array3(int a, int b, int c) {
|
||||
return new int[] {a, b, c};
|
||||
}
|
||||
|
||||
@QLFunction("concat")
|
||||
public String concat(String a, String b) {
|
||||
return a + b;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
BatchAddFunctionResult addResult = express4Runner.addObjFunction(new MyFunctionUtil());
|
||||
assertEquals(4, addResult.getSucc().size());
|
||||
Object result =
|
||||
express4Runner.execute("myAdd(1,2) + iAdd(5,6)", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(14, result);
|
||||
express4Runner.addStaticFunction(MyFunctionUtil.class);
|
||||
Object result1 =
|
||||
express4Runner.execute("arr3(5,9,10)[2]", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(10, result1);
|
||||
|
||||
Object result2 =
|
||||
express4Runner.execute("concat('aa', null)", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("aanull", result2);
|
||||
----
|
||||
|
||||
=== 通过QLExpress脚本添加
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
public static class JoinFunction implements QLFunctionalVarargs {
|
||||
@Override
|
||||
public Object call(Object... params) {
|
||||
return Arrays.stream(params).map(Object::toString).collect(Collectors.joining(","));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
=== 实现QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
express4Runner.addVarArgsFunction("join", new JoinFunction());
|
||||
Object resultFunction =
|
||||
express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("1,2,3", resultFunction);
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
express4Runner.addVarArgsFunction("join", new JoinFunction());
|
||||
Object resultFunction =
|
||||
express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("1,2,3", resultFunction);
|
||||
----
|
||||
|
||||
== 自定义操作符
|
||||
|
||||
=== 实现CustomBinaryOperator并设置优先级
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
runner.addOperator("?><", (left, right) -> left.get().toString() + right.get().toString(), QLPrecedences.ADD);
|
||||
Object r = runner.execute("1 ?>< 2 * 3", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
// precedence set to ADD, so multiply first, then custom operator: "1" + "6" => "16"
|
||||
assertEquals("16", r);
|
||||
----
|
||||
|
||||
=== 替换内置操作符
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
boolean ok = runner.replaceDefaultOperator("+",
|
||||
(left, right) -> Double.parseDouble(left.get().toString()) + Double.parseDouble(right.get().toString()));
|
||||
assertTrue(ok);
|
||||
Object r = runner.execute("'1.2' + '2.3'", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(3.5d, r);
|
||||
----
|
||||
|
||||
=== 使用 Java 函数式接口
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
express4Runner.addOperatorBiFunction("join", (left, right) -> left + "," + right);
|
||||
Object resultOperator =
|
||||
express4Runner.execute("1 join 2 join 3", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("1,2,3", resultOperator);
|
||||
----
|
||||
|
||||
=== 实现QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
runner.addOperator("join", params -> params[0] + "," + params[1]);
|
||||
Object r = runner.execute("1 join 2", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("1,2", r);
|
||||
----
|
||||
|
||||
== 扩展函数
|
||||
|
||||
=== 继承ExtensionFunction
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
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) {
|
||||
String originStr = (String)obj;
|
||||
return "Hello," + originStr;
|
||||
}
|
||||
};
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
express4Runner.addExtendFunction(helloFunction);
|
||||
Object result =
|
||||
express4Runner.execute("'jack'.hello()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("Hello,jack", result);
|
||||
----
|
||||
|
||||
=== 实现QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
// simpler way to define extension function
|
||||
express4Runner.addExtendFunction("add",
|
||||
Number.class,
|
||||
params -> ((Number)params[0]).intValue() + ((Number)params[1]).intValue());
|
||||
QLResult resultAdd = express4Runner.execute("1.add(2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS);
|
||||
assertEquals(3, resultAdd.getResult());
|
||||
----
|
||||
|
||||
== QLFunctionalVarargs:一个对象同时定义三类操作
|
||||
|
||||
同一个QLFunctionalVarargs对象可同时用作函数、操作符与扩展函数的实现,便于在多处复用统一的语义与实现。该能力来源于接口的可变参数设计,详见下方示例与接口定义。背景讨论参考 link:https://github.com/alibaba/QLExpress/issues/407[issue407]:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
QLFunctionalVarargs allInOne = params -> {
|
||||
// sum numbers no matter how many args
|
||||
double sum = 0d;
|
||||
for (Object p : params) {
|
||||
if (p instanceof Number) {
|
||||
sum += ((Number)p).doubleValue();
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
};
|
||||
|
||||
// as function
|
||||
runner.addVarArgsFunction("sumAll", allInOne);
|
||||
// as operator
|
||||
runner.addOperator("+&", allInOne);
|
||||
// as extension function: first arg is the receiver, followed by call arguments
|
||||
runner.addExtendFunction("plusAll", Number.class, allInOne);
|
||||
|
||||
Map<String, Object> ctx = new HashMap<>();
|
||||
Object rf = runner.execute("sumAll(1,2,3)", ctx, QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
Object ro = runner.execute("1 +& 4", ctx, QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
Object re = runner.execute("1.plusAll(5)", ctx, QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(6d, rf);
|
||||
assertEquals(5d, ro);
|
||||
assertEquals(6d, re);
|
||||
----
|
||||
|
||||
=== 接口定义
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
package com.alibaba.qlexpress4.api;
|
||||
|
||||
/**
|
||||
* Author: TaoKan
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface QLFunctionalVarargs {
|
||||
Object call(Object... params);
|
||||
}
|
||||
----
|
||||
|
||||
== 操作符与函数别名(亦支持关键字别名)
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
// add custom function zero
|
||||
express4Runner.addFunction("zero", (String ignore) -> 0);
|
||||
|
||||
// keyword alias
|
||||
assertTrue(express4Runner.addAlias("如果", "if"));
|
||||
assertTrue(express4Runner.addAlias("则", "then"));
|
||||
assertTrue(express4Runner.addAlias("否则", "else"));
|
||||
assertTrue(express4Runner.addAlias("返回", "return"));
|
||||
// operator alias
|
||||
assertTrue(express4Runner.addAlias("大于", ">"));
|
||||
// function alias
|
||||
assertTrue(express4Runner.addAlias("零", "zero"));
|
||||
|
||||
Map<String, Object> context = new HashMap<>();
|
||||
context.put("语文", 90);
|
||||
context.put("数学", 90);
|
||||
context.put("英语", 90);
|
||||
|
||||
Object result = express4Runner
|
||||
.execute("如果 (语文 + 数学 + 英语 大于 270) 则 {返回 1;} 否则 {返回 零();}", context, QLOptions.DEFAULT_OPTIONS)
|
||||
.getResult();
|
||||
assertEquals(0, result);
|
||||
----
|
||||
|
||||
== 说明与建议
|
||||
|
||||
- QLFunctionalVarargs 的定义模式下,扩展函数调用时实参列表首位是接收者对象,其后为调用参数;函数与操作符不含接收者。
|
||||
- 自定义操作符的优先级需根据表达式期望进行设置,避免与已有运算规则产生混淆。
|
||||
- 注解方式注册仅会处理公开方法,且重复名称将注册失败;批量注册返回结果中包含成功与失败清单。
|
||||
50
docs/execute-en-source.adoc
Normal file
50
docs/execute-en-source.adoc
Normal file
@@ -0,0 +1,50 @@
|
||||
:toc:
|
||||
|
||||
= Expression Execution
|
||||
|
||||
This section describes the different ways to execute expressions in QLExpress4.
|
||||
|
||||
== Use Map as the context
|
||||
|
||||
- `execute(String, Map<String,Object>, QLOptions)`
|
||||
- Description: According to the method comments, use a `Map` as the context; the keys in the `Map` are the variable names used in the script, and the values are the corresponding variable values.
|
||||
- Typical scenario: Build a `Map` from external business data and pass it in so the script can access variables directly by name.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=firstQl]
|
||||
----
|
||||
|
||||
== Use object fields as the context
|
||||
|
||||
- `execute(String, Object, QLOptions)`
|
||||
- Description: Use a plain Java object as the context; variable names in the script correspond to the object's field names (or accessible getters).
|
||||
- Typical scenario: When you already have a DTO/POJO that carries the context data, pass the object directly so the script can read its fields.
|
||||
|
||||
// Note: This references the test code for execution with an object context.
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=executeWithObject]
|
||||
----
|
||||
|
||||
== Use a set of aliased objects
|
||||
|
||||
- `executeWithAliasObjects(String, QLOptions, Object...)`
|
||||
- Description: Pass objects annotated with `@QLAlias`; the annotation value becomes the variable name in the context. Objects without the annotation are ignored.
|
||||
- Typical scenario: Expose object properties/methods with Chinese (or domain-specific) aliases so rule authors can write scripts more intuitively.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=qlAlias]
|
||||
----
|
||||
|
||||
== Use a custom `ExpressContext`
|
||||
|
||||
- `execute(String, ExpressContext, QLOptions)`
|
||||
- Description: This overload allows passing a custom context that implements `ExpressContext` (for example, a dynamic variable context). The other `execute` overloads ultimately delegate to this method.
|
||||
- Typical scenario: When you need on-demand, by-name evaluation of variable values, read/write isolation, or integration with external storage, a custom context is the most flexible choice.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=dynamicVar]
|
||||
----
|
||||
95
docs/execute-en.adoc
Normal file
95
docs/execute-en.adoc
Normal file
@@ -0,0 +1,95 @@
|
||||
:toc:
|
||||
|
||||
= Expression Execution
|
||||
|
||||
This section describes the different ways to execute expressions in QLExpress4.
|
||||
|
||||
== Use Map as the context
|
||||
|
||||
- `execute(String, Map<String,Object>, QLOptions)`
|
||||
- Description: According to the method comments, use a `Map` as the context; the keys in the `Map` are the variable names used in the script, and the values are the corresponding variable values.
|
||||
- Typical scenario: Build a `Map` from external business data and pass it in so the script can access variables directly by name.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
Map<String, Object> 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).getResult();
|
||||
assertEquals(7, result);
|
||||
----
|
||||
|
||||
== Use object fields as the context
|
||||
|
||||
- `execute(String, Object, QLOptions)`
|
||||
- Description: Use a plain Java object as the context; variable names in the script correspond to the object's field names (or accessible getters).
|
||||
- Typical scenario: When you already have a DTO/POJO that carries the context data, pass the object directly so the script can read its fields.
|
||||
|
||||
// Note: This references the test code for execution with an object context.
|
||||
[source,java,indent=0]
|
||||
----
|
||||
MyObj myObj = new MyObj();
|
||||
myObj.a = 1;
|
||||
myObj.b = "test";
|
||||
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
Object result = express4Runner.execute("a+b", myObj, QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("1test", result);
|
||||
----
|
||||
|
||||
== Use a set of aliased objects
|
||||
|
||||
- `executeWithAliasObjects(String, QLOptions, Object...)`
|
||||
- Description: Pass objects annotated with `@QLAlias`; the annotation value becomes the variable name in the context. Objects without the annotation are ignored.
|
||||
- Typical scenario: Expose object properties/methods with Chinese (or domain-specific) aliases so rule authors can write scripts more intuitively.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
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)
|
||||
.getResult();
|
||||
assertEquals(80, result.intValue());
|
||||
----
|
||||
|
||||
== Use a custom `ExpressContext`
|
||||
|
||||
- `execute(String, ExpressContext, QLOptions)`
|
||||
- Description: This overload allows passing a custom context that implements `ExpressContext` (for example, a dynamic variable context). The other `execute` overloads ultimately delegate to this method.
|
||||
- Typical scenario: When you need on-demand, by-name evaluation of variable values, read/write isolation, or integration with external storage, a custom context is the most flexible choice.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
|
||||
Map<String, Object> staticContext = new HashMap<>();
|
||||
staticContext.put("语文", 88);
|
||||
staticContext.put("数学", 99);
|
||||
staticContext.put("英语", 95);
|
||||
|
||||
QLOptions defaultOptions = QLOptions.DEFAULT_OPTIONS;
|
||||
DynamicVariableContext dynamicContext =
|
||||
new DynamicVariableContext(express4Runner, staticContext, defaultOptions);
|
||||
dynamicContext.put("平均成绩", "(语文+数学+英语)/3.0");
|
||||
dynamicContext.put("是否优秀", "平均成绩>90");
|
||||
|
||||
// dynamic var
|
||||
assertTrue((Boolean)express4Runner.execute("是否优秀", dynamicContext, defaultOptions).getResult());
|
||||
assertEquals(94,
|
||||
((Number)express4Runner.execute("平均成绩", dynamicContext, defaultOptions).getResult()).intValue());
|
||||
// static var
|
||||
assertEquals(187,
|
||||
((Number)express4Runner.execute("语文+数学", dynamicContext, defaultOptions).getResult()).intValue());
|
||||
----
|
||||
50
docs/execute-source.adoc
Normal file
50
docs/execute-source.adoc
Normal file
@@ -0,0 +1,50 @@
|
||||
:toc:
|
||||
|
||||
= 表达式执行
|
||||
|
||||
本节说明在 QLExpress4 中执行表达式的多种方式。
|
||||
|
||||
== 使用 Map 作为上下文
|
||||
|
||||
- `execute(String, Map<String,Object>, QLOptions)`
|
||||
- 说明:根据方法注释,使用 `Map` 作为上下文,`Map` 的 key 即为脚本中的变量名,value 为对应变量值。
|
||||
- 典型场景:从外部业务数据构造一个 `Map` 传入,直接在脚本里以变量名访问。
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=firstQl]
|
||||
----
|
||||
|
||||
== 使用对象字段作为上下文
|
||||
|
||||
- `execute(String, Object, QLOptions)`
|
||||
- 说明:根据方法注释,使用一个普通 Java 对象作为上下文,脚本中的变量名对应该对象的字段名(或可访问的 getter)。
|
||||
- 典型场景:已有一个承载上下文数据的 DTO/POJO,直接将对象传入以便脚本读取其字段。
|
||||
|
||||
// 说明:此处引用对象上下文执行的测试代码。
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=executeWithObject]
|
||||
----
|
||||
|
||||
== 使用带别名的对象集合
|
||||
|
||||
- `executeWithAliasObjects(String, QLOptions, Object...)`
|
||||
- 说明:根据方法注释,传入标记了 `@QLAlias` 的对象,注解值将作为上下文中的变量名;未标记的对象会被忽略。
|
||||
- 典型场景:以中文(或领域语言)别名暴露对象属性/方法,供规则编写者直观地编写脚本。
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=qlAlias]
|
||||
----
|
||||
|
||||
== 使用自定义 `ExpressContext`
|
||||
|
||||
- `execute(String, ExpressContext, QLOptions)`
|
||||
- 说明:该重载允许传入实现了 `ExpressContext` 的自定义上下文(例如动态变量上下文)。其它 `execute` 重载最终也会委托到此方法。
|
||||
- 典型场景:需要按需按名计算变量值、隔离读写、或接入外部存储等高级能力时,自定义上下文最灵活。
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=dynamicVar]
|
||||
----
|
||||
95
docs/execute.adoc
Normal file
95
docs/execute.adoc
Normal file
@@ -0,0 +1,95 @@
|
||||
:toc:
|
||||
|
||||
= 表达式执行
|
||||
|
||||
本节说明在 QLExpress4 中执行表达式的多种方式。
|
||||
|
||||
== 使用 Map 作为上下文
|
||||
|
||||
- `execute(String, Map<String,Object>, QLOptions)`
|
||||
- 说明:根据方法注释,使用 `Map` 作为上下文,`Map` 的 key 即为脚本中的变量名,value 为对应变量值。
|
||||
- 典型场景:从外部业务数据构造一个 `Map` 传入,直接在脚本里以变量名访问。
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
Map<String, Object> 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).getResult();
|
||||
assertEquals(7, result);
|
||||
----
|
||||
|
||||
== 使用对象字段作为上下文
|
||||
|
||||
- `execute(String, Object, QLOptions)`
|
||||
- 说明:根据方法注释,使用一个普通 Java 对象作为上下文,脚本中的变量名对应该对象的字段名(或可访问的 getter)。
|
||||
- 典型场景:已有一个承载上下文数据的 DTO/POJO,直接将对象传入以便脚本读取其字段。
|
||||
|
||||
// 说明:此处引用对象上下文执行的测试代码。
|
||||
[source,java,indent=0]
|
||||
----
|
||||
MyObj myObj = new MyObj();
|
||||
myObj.a = 1;
|
||||
myObj.b = "test";
|
||||
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
Object result = express4Runner.execute("a+b", myObj, QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("1test", result);
|
||||
----
|
||||
|
||||
== 使用带别名的对象集合
|
||||
|
||||
- `executeWithAliasObjects(String, QLOptions, Object...)`
|
||||
- 说明:根据方法注释,传入标记了 `@QLAlias` 的对象,注解值将作为上下文中的变量名;未标记的对象会被忽略。
|
||||
- 典型场景:以中文(或领域语言)别名暴露对象属性/方法,供规则编写者直观地编写脚本。
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
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)
|
||||
.getResult();
|
||||
assertEquals(80, result.intValue());
|
||||
----
|
||||
|
||||
== 使用自定义 `ExpressContext`
|
||||
|
||||
- `execute(String, ExpressContext, QLOptions)`
|
||||
- 说明:该重载允许传入实现了 `ExpressContext` 的自定义上下文(例如动态变量上下文)。其它 `execute` 重载最终也会委托到此方法。
|
||||
- 典型场景:需要按需按名计算变量值、隔离读写、或接入外部存储等高级能力时,自定义上下文最灵活。
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
|
||||
Map<String, Object> staticContext = new HashMap<>();
|
||||
staticContext.put("语文", 88);
|
||||
staticContext.put("数学", 99);
|
||||
staticContext.put("英语", 95);
|
||||
|
||||
QLOptions defaultOptions = QLOptions.DEFAULT_OPTIONS;
|
||||
DynamicVariableContext dynamicContext =
|
||||
new DynamicVariableContext(express4Runner, staticContext, defaultOptions);
|
||||
dynamicContext.put("平均成绩", "(语文+数学+英语)/3.0");
|
||||
dynamicContext.put("是否优秀", "平均成绩>90");
|
||||
|
||||
// dynamic var
|
||||
assertTrue((Boolean)express4Runner.execute("是否优秀", dynamicContext, defaultOptions).getResult());
|
||||
assertEquals(94,
|
||||
((Number)express4Runner.execute("平均成绩", dynamicContext, defaultOptions).getResult()).intValue());
|
||||
// static var
|
||||
assertEquals(187,
|
||||
((Number)express4Runner.execute("语文+数学", dynamicContext, defaultOptions).getResult()).intValue());
|
||||
----
|
||||
BIN
images/json_map.png
Normal file
BIN
images/json_map.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 180 KiB |
BIN
images/logo.png
Normal file
BIN
images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 685 KiB |
BIN
images/order_rules_cn.png
Normal file
BIN
images/order_rules_cn.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
BIN
images/qlexpress_support_group_qr_2026.jpg
Normal file
BIN
images/qlexpress_support_group_qr_2026.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 127 KiB |
312
pom.xml
Normal file
312
pom.xml
Normal file
@@ -0,0 +1,312 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.fantaibao</groupId>
|
||||
<artifactId>fantaibao-qlexpress4</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>4.0.7</version>
|
||||
<name>QLExpress</name>
|
||||
<description>QLExpress is a powerful, lightweight, dynamic language for the Java platform aimed at improving
|
||||
developers’ productivity in different business scenes.
|
||||
</description>
|
||||
<url>https://github.com/alibaba/QLExpress</url>
|
||||
<inceptionYear>2012</inceptionYear>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache 2</name>
|
||||
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
<comments>A business-friendly OSS license</comments>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:git@github.com:alibaba/QLExpress.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:alibaba/QLExpress.git</developerConnection>
|
||||
<url>git@github.com:alibaba/QLExpress.git</url>
|
||||
</scm>
|
||||
|
||||
<issueManagement>
|
||||
<url>https://github.com/alibaba/QLExpress/issues</url>
|
||||
<system>GitHub Issues</system>
|
||||
</issueManagement>
|
||||
|
||||
<organization>
|
||||
<name>Alibaba Group</name>
|
||||
<url>https://github.com/alibaba</url>
|
||||
</organization>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>tianqiao</id>
|
||||
<name>tianqiao</name>
|
||||
<email>tianqiao@alibaba-inc.com</email>
|
||||
</developer>
|
||||
<developer>
|
||||
<id>baoxingjie</id>
|
||||
<name>baoxingjie</name>
|
||||
<email>baoxingjie@126.com</email>
|
||||
</developer>
|
||||
<developer>
|
||||
<id>qianghui</id>
|
||||
<name>qianghui</name>
|
||||
<email>qhlhl2010@gmail.com</email>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.antlr</groupId>
|
||||
<artifactId>antlr4-runtime</artifactId>
|
||||
<version>4.9.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
<version>2.0.9</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- Spring Test dependency -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<version>5.3.30</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<version>5.3.30</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.pf4j</groupId>
|
||||
<artifactId>pf4j</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<!-- JaCoCo plugin -->
|
||||
<!-- <plugin>-->
|
||||
<!-- <groupId>org.jacoco</groupId>-->
|
||||
<!-- <artifactId>jacoco-maven-plugin</artifactId>-->
|
||||
<!-- <version>0.8.7</version>-->
|
||||
<!-- <executions>-->
|
||||
<!-- <execution>-->
|
||||
<!-- <id>pre-test</id>-->
|
||||
<!-- <goals>-->
|
||||
<!-- <goal>prepare-agent</goal>-->
|
||||
<!-- </goals>-->
|
||||
<!-- </execution>-->
|
||||
<!-- <execution>-->
|
||||
<!-- <id>post-test</id>-->
|
||||
<!-- <phase>test</phase>-->
|
||||
<!-- <goals>-->
|
||||
<!-- <goal>report</goal>-->
|
||||
<!-- </goals>-->
|
||||
<!-- </execution>-->
|
||||
<!-- </executions>-->
|
||||
<!-- </plugin>-->
|
||||
|
||||
<plugin>
|
||||
<groupId>com.diffplug.spotless</groupId>
|
||||
<artifactId>spotless-maven-plugin</artifactId>
|
||||
<version>2.0.3</version>
|
||||
<configuration>
|
||||
<formats>
|
||||
<format>
|
||||
<includes>
|
||||
<include>src/main/java/**/*.java</include>
|
||||
<include>src/test/java/**/*.java</include>
|
||||
</includes>
|
||||
<indent>
|
||||
<spaces>true</spaces>
|
||||
<spacesPerTab>4</spacesPerTab>
|
||||
</indent>
|
||||
</format>
|
||||
</formats>
|
||||
<java>
|
||||
<eclipse>
|
||||
<file>spotless_eclipse_formatter.xml</file>
|
||||
</eclipse>
|
||||
</java>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>2.1</version>
|
||||
<configuration>
|
||||
<attach>true</attach>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.1</version>
|
||||
<configuration>
|
||||
<aggregate>true</aggregate>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!-- GPG -->
|
||||
<!-- <plugin>-->
|
||||
<!-- <groupId>org.apache.maven.plugins</groupId>-->
|
||||
<!-- <artifactId>maven-gpg-plugin</artifactId>-->
|
||||
<!-- <version>1.6</version>-->
|
||||
<!-- <executions>-->
|
||||
<!-- <execution>-->
|
||||
<!-- <phase>verify</phase>-->
|
||||
<!-- <goals>-->
|
||||
<!-- <goal>sign</goal>-->
|
||||
<!-- </goals>-->
|
||||
<!-- </execution>-->
|
||||
<!-- </executions>-->
|
||||
<!-- <configuration>-->
|
||||
<!-- <!– Prevent gpg from using pinentry programs –>-->
|
||||
<!-- <gpgArguments>-->
|
||||
<!-- <arg>--pinentry-mode</arg>-->
|
||||
<!-- <arg>loopback</arg>-->
|
||||
<!-- </gpgArguments>-->
|
||||
<!-- </configuration>-->
|
||||
<!-- </plugin>-->
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.diffplug.spotless</groupId>
|
||||
<artifactId>spotless-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- <plugin>-->
|
||||
<!-- <groupId>org.sonatype.plugins</groupId>-->
|
||||
<!-- <artifactId>nexus-staging-maven-plugin</artifactId>-->
|
||||
<!-- <version>1.6.7</version>-->
|
||||
<!-- <extensions>true</extensions>-->
|
||||
<!-- <configuration>-->
|
||||
<!-- <serverId>ossrh</serverId>-->
|
||||
<!-- <nexusUrl>https://ossrh-staging-api.central.sonatype.com/</nexusUrl>-->
|
||||
<!-- <autoReleaseAfterClose>true</autoReleaseAfterClose>-->
|
||||
<!-- </configuration>-->
|
||||
<!-- </plugin>-->
|
||||
<plugin>
|
||||
<groupId>org.antlr</groupId>
|
||||
<artifactId>antlr4-maven-plugin</artifactId>
|
||||
<version>4.9.3</version>
|
||||
<configuration>
|
||||
<listener>true</listener>
|
||||
<visitor>true</visitor>
|
||||
<outputDirectory>${project.build.directory}/generated-sources/antlr4/com/alibaba/qlexpress4/aparser</outputDirectory>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<!-- antlr4 默认绑定在 generate-sources 阶段 -->
|
||||
<goals>
|
||||
<goal>antlr4</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>org.antlr.v4.runtime</pattern>
|
||||
<shadedPattern>com.alibaba.qlexpress4.a4runtime</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!-- JaCoCo plugin -->
|
||||
<!-- <plugin>-->
|
||||
<!-- <groupId>org.jacoco</groupId>-->
|
||||
<!-- <artifactId>jacoco-maven-plugin</artifactId>-->
|
||||
<!-- </plugin>-->
|
||||
</plugins>
|
||||
</build>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>deploy</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
</plugin>
|
||||
<!-- <plugin>-->
|
||||
<!-- <groupId>org.apache.maven.plugins</groupId>-->
|
||||
<!-- <artifactId>maven-gpg-plugin</artifactId>-->
|
||||
<!-- </plugin>-->
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<!--增加私服仓库配置-->
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<!--必须与 settings.xml 的 id 一致-->
|
||||
<id>fantaibao-fantaibao-maven-repository</id>
|
||||
<name>maven-repository</name>
|
||||
<url>http://192.168.3.25:18081/nexus/repository/maven-releases/</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
|
||||
</project>
|
||||
|
||||
295
spotless_eclipse_formatter.xml
Normal file
295
spotless_eclipse_formatter.xml
Normal file
@@ -0,0 +1,295 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<profiles version="12">
|
||||
<profile kind="CodeFormatterProfile" name="CodeFormatter" version="12">
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.compiler.source" value="1.8"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="120"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="20"/>
|
||||
<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="37"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.8"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="88"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="37"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.8"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
|
||||
</profile>
|
||||
</profiles>
|
||||
436
src/main/antlr4/QLParser.g4
Normal file
436
src/main/antlr4/QLParser.g4
Normal file
@@ -0,0 +1,436 @@
|
||||
parser grammar QLParser;
|
||||
|
||||
options {
|
||||
tokenVocab = QLexer;
|
||||
}
|
||||
|
||||
@header {
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
import com.alibaba.qlexpress4.aparser.ParserOperatorManager.OpType;
|
||||
import static com.alibaba.qlexpress4.aparser.ParserOperatorManager.OpType.*;
|
||||
import static com.alibaba.qlexpress4.QLPrecedences.*;
|
||||
import static com.alibaba.qlexpress4.aparser.InterpolationMode.*;
|
||||
}
|
||||
|
||||
@members {
|
||||
protected boolean isOpType(String lexeme, OpType opType) {
|
||||
return false;
|
||||
}
|
||||
protected Integer precedence(String lexeme) {
|
||||
return 0;
|
||||
}
|
||||
protected InterpolationMode getInterpolationMode() {
|
||||
return SCRIPT;
|
||||
}
|
||||
protected boolean isStrictNewLines() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// grammar
|
||||
|
||||
program
|
||||
: (newlines? importDeclaration)* newlines? blockStatements? EOF
|
||||
;
|
||||
|
||||
blockStatements
|
||||
: blockStatement+
|
||||
;
|
||||
|
||||
newlines : NEWLINE+;
|
||||
|
||||
nextStatement
|
||||
: {_input.LA(1) == Token.EOF || _input.LA(1) == QLexer.RBRACE}? | ';' | {isStrictNewLines()}? NEWLINE;
|
||||
|
||||
blockStatement
|
||||
: localVariableDeclaration ';' # localVariableDeclarationStatement
|
||||
| THROW expression nextStatement # throwStatement
|
||||
| WHILE '(' newlines? expression newlines? ')' '{' newlines? blockStatements? newlines? '}' # whileStatement
|
||||
| FOR '(' newlines? forInit (forCondition=expression)? ';' newlines? (forUpdate=expression)? newlines? ')' '{' newlines? blockStatements? newlines? '}' # traditionalForStatement
|
||||
| FOR '(' newlines? declType? varId ':' expression newlines? ')' '{' newlines? blockStatements? newlines? '}' # forEachStatement
|
||||
| FUNCTION varId '(' newlines? formalOrInferredParameterList? newlines? ')' LBRACE newlines? blockStatements? newlines? RBRACE # functionStatement
|
||||
| MACRO varId LBRACE newlines? blockStatements? newlines? RBRACE # macroStatement
|
||||
| (BREAK | CONTINUE) nextStatement # breakContinueStatement
|
||||
| RETURN expression? nextStatement # returnStatement
|
||||
| (';' | NEWLINE) # emptyStatement
|
||||
| expression nextStatement # expressionStatement
|
||||
;
|
||||
|
||||
localVariableDeclaration
|
||||
: declType variableDeclaratorList
|
||||
;
|
||||
|
||||
forInit
|
||||
: localVariableDeclaration ';'
|
||||
| expression ';'
|
||||
| ';'
|
||||
;
|
||||
|
||||
variableDeclaratorList
|
||||
: variableDeclarator (newlines? ',' newlines? variableDeclarator)*
|
||||
;
|
||||
|
||||
variableDeclarator
|
||||
: variableDeclaratorId (EQ newlines? variableInitializer)?
|
||||
;
|
||||
|
||||
variableDeclaratorId
|
||||
: varId dims?
|
||||
;
|
||||
|
||||
variableInitializer
|
||||
: expression
|
||||
| arrayInitializer
|
||||
;
|
||||
|
||||
arrayInitializer
|
||||
: LBRACE newlines? variableInitializerList? newlines? RBRACE
|
||||
;
|
||||
|
||||
variableInitializerList
|
||||
: variableInitializer (newlines? ',' newlines? variableInitializer)* ','?
|
||||
;
|
||||
|
||||
// decl type
|
||||
|
||||
declType
|
||||
: primitiveType dims?
|
||||
| clsType dims?
|
||||
;
|
||||
|
||||
declTypeNoArr
|
||||
: primitiveType
|
||||
| clsType
|
||||
;
|
||||
|
||||
primitiveType
|
||||
: 'byte'
|
||||
| 'short'
|
||||
| 'int'
|
||||
| 'long'
|
||||
| 'float'
|
||||
| 'double'
|
||||
| 'boolean'
|
||||
| 'char'
|
||||
;
|
||||
|
||||
referenceType
|
||||
: clsType dims?
|
||||
| primitiveType dims
|
||||
;
|
||||
|
||||
dims
|
||||
: LBRACK RBRACK (LBRACK RBRACK)*
|
||||
;
|
||||
|
||||
clsTypeNoTypeArguments
|
||||
: varId ('.' varId)*
|
||||
;
|
||||
|
||||
clsType
|
||||
: varId ('.' varId)* typeArguments?
|
||||
;
|
||||
|
||||
typeArguments
|
||||
: LT newlines? typeArgumentList? newlines? (GT | RIGHSHIFT | URSHIFT)?
|
||||
| NOEQ
|
||||
;
|
||||
|
||||
typeArgumentList
|
||||
: typeArgument (newlines? ',' newlines? typeArgument)*
|
||||
;
|
||||
|
||||
typeArgument
|
||||
: referenceType
|
||||
| wildcard
|
||||
;
|
||||
|
||||
wildcard
|
||||
: '?' wildcardBounds?
|
||||
;
|
||||
|
||||
wildcardBounds
|
||||
: 'extends' referenceType
|
||||
| 'super' referenceType
|
||||
;
|
||||
|
||||
// expression
|
||||
|
||||
expression
|
||||
: leftHandSide assignOperator newlines? expression
|
||||
| ternaryExpr
|
||||
;
|
||||
|
||||
leftHandSide
|
||||
: varId (newlines? pathPart)*
|
||||
;
|
||||
|
||||
ternaryExpr
|
||||
: condition=baseExpr[1] (QUESTION newlines? thenExpr=baseExpr[0] COLON newlines? elseExpr=expression)?
|
||||
;
|
||||
|
||||
baseExpr [int p]
|
||||
: primary ({_input.LA(1) != Token.EOF && (!isStrictNewLines() || _input.LA(1) != QLexer.NEWLINE) &&
|
||||
isOpType(_input.LT(1).getText(), MIDDLE) && precedence(_input.LT(1).getText()) >= $p}? leftAsso)*
|
||||
;
|
||||
|
||||
leftAsso
|
||||
: binaryop newlines? baseExpr[precedence(_localctx.binaryop().getStart().getText()) + 1];
|
||||
|
||||
binaryop
|
||||
: opId | varId
|
||||
;
|
||||
|
||||
// primary
|
||||
|
||||
primary
|
||||
: (prefixExpress)? primaryNoFixPathable (newlines? pathPart)* (suffixExpress)?
|
||||
| primaryNoFixNonPathable
|
||||
;
|
||||
|
||||
prefixExpress
|
||||
: {_input.LT(1).getType() != Token.EOF && isOpType(_input.LT(1).getText(), PREFIX)}? opId
|
||||
;
|
||||
|
||||
suffixExpress
|
||||
: {_input.LT(1).getType() != Token.EOF && isOpType(_input.LT(1).getText(), SUFFIX)}? opId
|
||||
;
|
||||
|
||||
primaryNoFixPathable
|
||||
: literal # constExpr
|
||||
| '(' newlines? declType newlines? ')' primary # castExpr
|
||||
| '(' newlines? expression newlines? ')' # groupExpr
|
||||
| NEW varId ('.' varId)* typeArguments? '(' newlines? argumentList? newlines? ')' # newObjExpr
|
||||
| NEW declTypeNoArr dimExprs # newEmptyArrExpr
|
||||
| NEW declTypeNoArr dims arrayInitializer # newInitArrExpr
|
||||
| varId # varIdExpr
|
||||
| primitiveType # typeExpr
|
||||
| '[' newlines? listItems? newlines? ']' # listExpr
|
||||
| LBRACE newlines? mapEntries newlines? RBRACE # mapExpr
|
||||
| LBRACE newlines? blockStatements? newlines? RBRACE # blockExpr
|
||||
| SELECTOR_START SelectorVariable_VANME # contextSelectExpr
|
||||
;
|
||||
|
||||
primaryNoFixNonPathable
|
||||
: qlIf # ifExpr
|
||||
| TRY LBRACE newlines? blockStatements? newlines? RBRACE tryCatches? (newlines? tryFinally)? # tryCatchExpr
|
||||
| lambdaParameters ARROW newlines? ( LBRACE newlines? blockStatements? newlines? RBRACE | expression) # lambdaExpr
|
||||
;
|
||||
|
||||
qlIf : IF '(' newlines? condition=expression newlines? ')' newlines? THEN? newlines? thenBody (newlines? ELSE newlines? elseBody)?;
|
||||
|
||||
thenBody
|
||||
: LBRACE newlines? blockStatements? newlines? RBRACE
|
||||
| expression
|
||||
| blockStatement
|
||||
;
|
||||
|
||||
elseBody
|
||||
: LBRACE newlines? blockStatements? newlines? RBRACE
|
||||
// if ... else ... if ...
|
||||
| qlIf
|
||||
| expression
|
||||
| blockStatement
|
||||
;
|
||||
|
||||
listItems
|
||||
: expression (newlines? ',' newlines? expression)* ','?
|
||||
;
|
||||
|
||||
dimExprs
|
||||
: ('[' newlines? expression newlines? ']')+
|
||||
;
|
||||
|
||||
tryCatches
|
||||
: tryCatch (newlines? tryCatch)*
|
||||
;
|
||||
|
||||
tryCatch
|
||||
: 'catch' '(' catchParams ')' LBRACE newlines? blockStatements? newlines? RBRACE
|
||||
;
|
||||
|
||||
catchParams
|
||||
: (declType ('|' declType)*)? varId
|
||||
;
|
||||
|
||||
tryFinally
|
||||
: FINALLY LBRACE newlines? blockStatements? newlines? RBRACE
|
||||
;
|
||||
|
||||
mapEntries
|
||||
: ':'
|
||||
| mapEntry (',' newlines? mapEntry)* ','?
|
||||
;
|
||||
|
||||
mapEntry
|
||||
: mapKey newlines? ':' newlines? mapValue
|
||||
;
|
||||
|
||||
mapValue
|
||||
: {_input.LT(-2).getText().equals("'@class'")}? QuoteStringLiteral # clsValue
|
||||
| expression # eValue
|
||||
;
|
||||
|
||||
mapKey
|
||||
: idMapKey # idKey
|
||||
| doubleQuoteStringLiteral # stringKey
|
||||
| QuoteStringLiteral # quoteStringKey
|
||||
;
|
||||
|
||||
idMapKey
|
||||
: varId
|
||||
| FOR
|
||||
| IF
|
||||
| ELSE
|
||||
| WHILE
|
||||
| BREAK
|
||||
| CONTINUE
|
||||
| RETURN
|
||||
| FUNCTION
|
||||
| MACRO
|
||||
| IMPORT
|
||||
| STATIC
|
||||
| NEW
|
||||
| BYTE
|
||||
| SHORT
|
||||
| INT
|
||||
| LONG
|
||||
| FLOAT
|
||||
| DOUBLE
|
||||
| CHAR
|
||||
| BOOL
|
||||
| NULL
|
||||
| TRUE
|
||||
| FALSE
|
||||
| EXTENDS
|
||||
| SUPER
|
||||
| TRY
|
||||
| CATCH
|
||||
| FINALLY
|
||||
| THROW
|
||||
| CLASS
|
||||
| THIS
|
||||
;
|
||||
|
||||
pathPart
|
||||
: '.' varId '(' newlines? argumentList? newlines? ')' # methodInvoke
|
||||
| OPTIONAL_CHAINING varId '(' newlines? argumentList? newlines? ')' # optionalMethodInvoke
|
||||
| SPREAD_CHAINING varId '(' newlines? argumentList? newlines? ')' # spreadMethodInvoke
|
||||
| '.' fieldId # fieldAccess
|
||||
| OPTIONAL_CHAINING fieldId # optionalFieldAccess
|
||||
| SPREAD_CHAINING fieldId # spreadFieldAccess
|
||||
| DCOLON varId # methodAccess
|
||||
| '(' newlines? argumentList? newlines? ')' # callExpr
|
||||
| '[' newlines? indexValueExpr? newlines? ']' # indexExpr
|
||||
| {isOpType(_input.LT(1).getText(), MIDDLE) && precedence(_input.LT(1).getText()) == GROUP}? opId newlines? varId # customPath
|
||||
;
|
||||
|
||||
fieldId
|
||||
: varId
|
||||
| CLASS
|
||||
| QuoteStringLiteral
|
||||
;
|
||||
|
||||
indexValueExpr
|
||||
: expression # singleIndex
|
||||
| start=expression? newlines? ':' newlines? end=expression? # sliceIndex
|
||||
;
|
||||
|
||||
argumentList
|
||||
: expression (newlines? ',' newlines? expression)*
|
||||
;
|
||||
|
||||
literal
|
||||
: IntegerLiteral
|
||||
| FloatingPointLiteral
|
||||
| IntegerOrFloatingLiteral
|
||||
| boolenLiteral
|
||||
| QuoteStringLiteral
|
||||
| doubleQuoteStringLiteral
|
||||
| NULL
|
||||
;
|
||||
|
||||
doubleQuoteStringLiteral
|
||||
: {getInterpolationMode() == DISABLE}? DOUBLE_QUOTE StaticStringCharacters? DOUBLE_QUOTE
|
||||
| DOUBLE_QUOTE (DyStrText | stringExpression)* DOUBLE_QUOTE
|
||||
;
|
||||
|
||||
stringExpression
|
||||
: {getInterpolationMode() == SCRIPT}? DyStrExprStart newlines? expression newlines? RBRACE
|
||||
| {getInterpolationMode() == VARIABLE}? DyStrExprStart SelectorVariable_VANME
|
||||
;
|
||||
|
||||
boolenLiteral
|
||||
: TRUE
|
||||
| FALSE
|
||||
;
|
||||
|
||||
lambdaParameters
|
||||
: varId
|
||||
| '(' formalOrInferredParameterList? ')'
|
||||
;
|
||||
|
||||
formalOrInferredParameterList
|
||||
: formalOrInferredParameter (newlines? ',' newlines? formalOrInferredParameter)*
|
||||
;
|
||||
|
||||
formalOrInferredParameter
|
||||
: declType? varId
|
||||
;
|
||||
|
||||
// import (not support import static now)
|
||||
|
||||
importDeclaration
|
||||
// import xxx
|
||||
: IMPORT varId ('.' varId)* ';' # importCls
|
||||
// import .*
|
||||
| IMPORT varId ('.' varId)* (DOT MUL | DOTMUL) ';' # importPack
|
||||
;
|
||||
|
||||
// id
|
||||
|
||||
assignOperator
|
||||
: EQ
|
||||
| RIGHSHIFT_ASSGIN
|
||||
| URSHIFT_ASSGIN
|
||||
| LSHIFT_ASSGIN
|
||||
| ADD_ASSIGN
|
||||
| SUB_ASSIGN
|
||||
| AND_ASSIGN
|
||||
| OR_ASSIGN
|
||||
| MUL_ASSIGN
|
||||
| MOD_ASSIGN
|
||||
| DIV_ASSIGN
|
||||
| XOR_ASSIGN
|
||||
;
|
||||
|
||||
opId
|
||||
: GT
|
||||
| LT
|
||||
| GE
|
||||
| LE
|
||||
| BANG
|
||||
| TILDE
|
||||
| ADD
|
||||
| SUB
|
||||
| MUL
|
||||
| DIV
|
||||
| INC
|
||||
| DEC
|
||||
| DOTMUL
|
||||
| NOEQ
|
||||
| RIGHSHIFT
|
||||
| URSHIFT
|
||||
| LEFTSHIFT
|
||||
| BIT_AND
|
||||
| BIT_OR
|
||||
| MOD
|
||||
| CARET
|
||||
| assignOperator
|
||||
| OPID
|
||||
;
|
||||
|
||||
varId
|
||||
: ID
|
||||
| FUNCTION
|
||||
;
|
||||
1196
src/main/antlr4/QLexer.g4
Normal file
1196
src/main/antlr4/QLexer.g4
Normal file
File diff suppressed because it is too large
Load Diff
180
src/main/antlr4/QLexer.tokens
Normal file
180
src/main/antlr4/QLexer.tokens
Normal file
@@ -0,0 +1,180 @@
|
||||
FOR=1
|
||||
IF=2
|
||||
ELSE=3
|
||||
WHILE=4
|
||||
BREAK=5
|
||||
CONTINUE=6
|
||||
RETURN=7
|
||||
FUNCTION=8
|
||||
MACRO=9
|
||||
IMPORT=10
|
||||
STATIC=11
|
||||
NEW=12
|
||||
BYTE=13
|
||||
SHORT=14
|
||||
INT=15
|
||||
LONG=16
|
||||
FLOAT=17
|
||||
DOUBLE=18
|
||||
CHAR=19
|
||||
BOOL=20
|
||||
NULL=21
|
||||
TRUE=22
|
||||
FALSE=23
|
||||
EXTENDS=24
|
||||
SUPER=25
|
||||
TRY=26
|
||||
CATCH=27
|
||||
FINALLY=28
|
||||
THROW=29
|
||||
THEN=30
|
||||
CLASS=31
|
||||
THIS=32
|
||||
QuoteStringLiteral=33
|
||||
IntegerLiteral=34
|
||||
FloatingPointLiteral=35
|
||||
IntegerOrFloatingLiteral=36
|
||||
LPAREN=37
|
||||
RPAREN=38
|
||||
LBRACE=39
|
||||
RBRACE=40
|
||||
LBRACK=41
|
||||
RBRACK=42
|
||||
DOT=43
|
||||
ARROW=44
|
||||
SEMI=45
|
||||
COMMA=46
|
||||
QUESTION=47
|
||||
COLON=48
|
||||
DCOLON=49
|
||||
GT=50
|
||||
LT=51
|
||||
EQ=52
|
||||
NOEQ=53
|
||||
RIGHSHIFT_ASSGIN=54
|
||||
RIGHSHIFT=55
|
||||
OPTIONAL_CHAINING=56
|
||||
SPREAD_CHAINING=57
|
||||
URSHIFT_ASSGIN=58
|
||||
URSHIFT=59
|
||||
LSHIFT_ASSGIN=60
|
||||
LEFTSHIFT=61
|
||||
GE=62
|
||||
LE=63
|
||||
DOTMUL=64
|
||||
CARET=65
|
||||
ADD_ASSIGN=66
|
||||
SUB_ASSIGN=67
|
||||
AND_ASSIGN=68
|
||||
OR_ASSIGN=69
|
||||
MUL_ASSIGN=70
|
||||
MOD_ASSIGN=71
|
||||
DIV_ASSIGN=72
|
||||
XOR_ASSIGN=73
|
||||
BANG=74
|
||||
TILDE=75
|
||||
ADD=76
|
||||
SUB=77
|
||||
MUL=78
|
||||
DIV=79
|
||||
BIT_AND=80
|
||||
BIT_OR=81
|
||||
MOD=82
|
||||
INC=83
|
||||
DEC=84
|
||||
NEWLINE=85
|
||||
WS=86
|
||||
COMMENT=87
|
||||
LINE_COMMENT=88
|
||||
OPID=89
|
||||
SELECTOR_START=90
|
||||
ID=91
|
||||
DOUBLE_QUOTE=92
|
||||
CATCH_ALL=93
|
||||
StaticStringCharacters=94
|
||||
DyStrExprStart=95
|
||||
DyStrText=96
|
||||
SelectorVariable_VANME=97
|
||||
StrExpr_WS=98
|
||||
'for'=1
|
||||
'if'=2
|
||||
'else'=3
|
||||
'while'=4
|
||||
'break'=5
|
||||
'continue'=6
|
||||
'return'=7
|
||||
'function'=8
|
||||
'macro'=9
|
||||
'import'=10
|
||||
'static'=11
|
||||
'new'=12
|
||||
'byte'=13
|
||||
'short'=14
|
||||
'int'=15
|
||||
'long'=16
|
||||
'float'=17
|
||||
'double'=18
|
||||
'char'=19
|
||||
'boolean'=20
|
||||
'null'=21
|
||||
'true'=22
|
||||
'false'=23
|
||||
'extends'=24
|
||||
'super'=25
|
||||
'try'=26
|
||||
'catch'=27
|
||||
'finally'=28
|
||||
'throw'=29
|
||||
'then'=30
|
||||
'class'=31
|
||||
'this'=32
|
||||
'('=37
|
||||
')'=38
|
||||
'{'=39
|
||||
'}'=40
|
||||
'['=41
|
||||
']'=42
|
||||
'.'=43
|
||||
'->'=44
|
||||
';'=45
|
||||
','=46
|
||||
'?'=47
|
||||
':'=48
|
||||
'::'=49
|
||||
'>'=50
|
||||
'<'=51
|
||||
'='=52
|
||||
'<>'=53
|
||||
'>>='=54
|
||||
'>>'=55
|
||||
'?.'=56
|
||||
'*.'=57
|
||||
'>>>='=58
|
||||
'>>>'=59
|
||||
'<<='=60
|
||||
'<<'=61
|
||||
'>='=62
|
||||
'<='=63
|
||||
'.*'=64
|
||||
'^'=65
|
||||
'+='=66
|
||||
'-='=67
|
||||
'&='=68
|
||||
'|='=69
|
||||
'*='=70
|
||||
'%='=71
|
||||
'/='=72
|
||||
'^='=73
|
||||
'!'=74
|
||||
'~'=75
|
||||
'+'=76
|
||||
'-'=77
|
||||
'*'=78
|
||||
'/'=79
|
||||
'&'=80
|
||||
'|'=81
|
||||
'%'=82
|
||||
'++'=83
|
||||
'--'=84
|
||||
'"'=92
|
||||
'${'=95
|
||||
69
src/main/java/com/alibaba/qlexpress4/CheckOptions.java
Normal file
69
src/main/java/com/alibaba/qlexpress4/CheckOptions.java
Normal file
@@ -0,0 +1,69 @@
|
||||
package com.alibaba.qlexpress4;
|
||||
|
||||
import com.alibaba.qlexpress4.operator.OperatorCheckStrategy;
|
||||
|
||||
/**
|
||||
* Script validation configuration class
|
||||
* Used to configure restriction rules during script validation
|
||||
*
|
||||
* @author QLExpress Team
|
||||
*/
|
||||
public class CheckOptions {
|
||||
|
||||
/**
|
||||
* Operator check strategy for script validation
|
||||
* default OperatorCheckStrategy.allowAll()
|
||||
*/
|
||||
private final OperatorCheckStrategy operatorCheckStrategy;
|
||||
|
||||
/**
|
||||
* Whether to disable function calls in the script
|
||||
* default false
|
||||
*/
|
||||
private final boolean disableFunctionCalls;
|
||||
|
||||
/**
|
||||
* Default validation options
|
||||
*/
|
||||
public static final CheckOptions DEFAULT_OPTIONS = CheckOptions.builder().build();
|
||||
|
||||
private CheckOptions(OperatorCheckStrategy operatorCheckStrategy, boolean disableFunctionCalls) {
|
||||
this.operatorCheckStrategy = operatorCheckStrategy;
|
||||
this.disableFunctionCalls = disableFunctionCalls;
|
||||
}
|
||||
|
||||
public OperatorCheckStrategy getCheckStrategy() {
|
||||
return operatorCheckStrategy;
|
||||
}
|
||||
|
||||
public boolean isDisableFunctionCalls() {
|
||||
return disableFunctionCalls;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private OperatorCheckStrategy operatorCheckStrategy = OperatorCheckStrategy.allowAll();
|
||||
|
||||
private boolean disableFunctionCalls = false;
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
public Builder operatorCheckStrategy(OperatorCheckStrategy operatorCheckStrategy) {
|
||||
this.operatorCheckStrategy = operatorCheckStrategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder disableFunctionCalls(boolean disableFunctionCalls) {
|
||||
this.disableFunctionCalls = disableFunctionCalls;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CheckOptions build() {
|
||||
return new CheckOptions(operatorCheckStrategy, disableFunctionCalls);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/main/java/com/alibaba/qlexpress4/ClassSupplier.java
Normal file
11
src/main/java/com/alibaba/qlexpress4/ClassSupplier.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package com.alibaba.qlexpress4;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ClassSupplier {
|
||||
|
||||
Class<?> loadCls(String clsQualifiedName);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.alibaba.qlexpress4;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class DefaultClassSupplier implements ClassSupplier {
|
||||
|
||||
private static final DefaultClassSupplier INSTANCE = new DefaultClassSupplier();
|
||||
|
||||
public static DefaultClassSupplier getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private final Map<String, Optional<Class<?>>> cache = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* @param clsQualifiedName qualified name of class
|
||||
* @return loaded class
|
||||
*/
|
||||
@Override
|
||||
public Class<?> loadCls(String clsQualifiedName) {
|
||||
Optional<Class<?>> clsOp = cache.computeIfAbsent(clsQualifiedName, this::loadClsInner);
|
||||
return clsOp.orElse(null);
|
||||
}
|
||||
|
||||
private Optional<Class<?>> loadClsInner(String clsQualifiedName) {
|
||||
try {
|
||||
Class<?> aClass = Class.forName(clsQualifiedName);
|
||||
return Optional.of(aClass);
|
||||
}
|
||||
catch (ClassNotFoundException | NoClassDefFoundError e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
751
src/main/java/com/alibaba/qlexpress4/Express4Runner.java
Normal file
751
src/main/java/com/alibaba/qlexpress4/Express4Runner.java
Normal file
@@ -0,0 +1,751 @@
|
||||
package com.alibaba.qlexpress4;
|
||||
|
||||
import com.alibaba.qlexpress4.aparser.CheckVisitor;
|
||||
import com.alibaba.qlexpress4.aparser.GeneratorScope;
|
||||
import com.alibaba.qlexpress4.aparser.ImportManager;
|
||||
import com.alibaba.qlexpress4.aparser.MacroDefine;
|
||||
import com.alibaba.qlexpress4.aparser.OutFunctionVisitor;
|
||||
import com.alibaba.qlexpress4.aparser.OutVarAttrsVisitor;
|
||||
import com.alibaba.qlexpress4.aparser.OutVarNamesVisitor;
|
||||
import com.alibaba.qlexpress4.aparser.QCompileCache;
|
||||
import com.alibaba.qlexpress4.aparser.QLParser;
|
||||
import com.alibaba.qlexpress4.aparser.QvmInstructionVisitor;
|
||||
import com.alibaba.qlexpress4.aparser.SyntaxTreeFactory;
|
||||
import com.alibaba.qlexpress4.aparser.TraceExpressionVisitor;
|
||||
import com.alibaba.qlexpress4.aparser.compiletimefunction.CompileTimeFunction;
|
||||
import com.alibaba.qlexpress4.api.BatchAddFunctionResult;
|
||||
import com.alibaba.qlexpress4.api.QLFunctionalVarargs;
|
||||
import com.alibaba.qlexpress4.exception.PureErrReporter;
|
||||
import com.alibaba.qlexpress4.exception.QLException;
|
||||
import com.alibaba.qlexpress4.exception.QLSyntaxException;
|
||||
import com.alibaba.qlexpress4.runtime.DelegateQContext;
|
||||
import com.alibaba.qlexpress4.runtime.QLambda;
|
||||
import com.alibaba.qlexpress4.runtime.QLambdaDefinitionInner;
|
||||
import com.alibaba.qlexpress4.runtime.QLambdaTrace;
|
||||
import com.alibaba.qlexpress4.runtime.QvmGlobalScope;
|
||||
import com.alibaba.qlexpress4.runtime.QvmRuntime;
|
||||
import com.alibaba.qlexpress4.runtime.ReflectLoader;
|
||||
import com.alibaba.qlexpress4.runtime.Value;
|
||||
import com.alibaba.qlexpress4.runtime.context.ExpressContext;
|
||||
import com.alibaba.qlexpress4.runtime.context.MapExpressContext;
|
||||
import com.alibaba.qlexpress4.runtime.context.ObjectFieldExpressContext;
|
||||
import com.alibaba.qlexpress4.runtime.context.QLAliasContext;
|
||||
import com.alibaba.qlexpress4.runtime.function.CustomFunction;
|
||||
import com.alibaba.qlexpress4.runtime.function.ExtensionFunction;
|
||||
import com.alibaba.qlexpress4.runtime.function.QMethodFunction;
|
||||
import com.alibaba.qlexpress4.runtime.instruction.QLInstruction;
|
||||
import com.alibaba.qlexpress4.runtime.operator.CustomBinaryOperator;
|
||||
import com.alibaba.qlexpress4.runtime.operator.OperatorManager;
|
||||
import com.alibaba.qlexpress4.runtime.trace.ExpressionTrace;
|
||||
import com.alibaba.qlexpress4.runtime.trace.QTraces;
|
||||
import com.alibaba.qlexpress4.runtime.trace.TracePointTree;
|
||||
import com.alibaba.qlexpress4.utils.BasicUtil;
|
||||
import com.alibaba.qlexpress4.utils.QLFunctionUtil;
|
||||
import org.antlr.v4.runtime.dfa.DFA;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class Express4Runner {
|
||||
private final OperatorManager operatorManager = new OperatorManager();
|
||||
|
||||
private final Map<String, Future<QCompileCache>> compileCache = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<String, CustomFunction> userDefineFunction = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<String, CompileTimeFunction> compileTimeFunctions = new ConcurrentHashMap<>();
|
||||
|
||||
private final GeneratorScope globalScope = new GeneratorScope(null, "global", new ConcurrentHashMap<>());
|
||||
|
||||
private final ReflectLoader reflectLoader;
|
||||
|
||||
private final InitOptions initOptions;
|
||||
|
||||
public Express4Runner(InitOptions initOptions) {
|
||||
this.initOptions = initOptions;
|
||||
this.reflectLoader = new ReflectLoader(initOptions.getSecurityStrategy(), initOptions.isAllowPrivateAccess());
|
||||
SyntaxTreeFactory.warmUp();
|
||||
}
|
||||
|
||||
public CustomFunction getFunction(String functionName) {
|
||||
return userDefineFunction.get(functionName);
|
||||
}
|
||||
|
||||
public CompileTimeFunction getCompileTimeFunction(String functionName) {
|
||||
return compileTimeFunctions.get(functionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the script with variables set in the context; the map key corresponds to the
|
||||
* variable name referenced in the script.
|
||||
*
|
||||
* @param script the script content to execute
|
||||
* @param context variables for execution, keyed by variable name
|
||||
* @param qlOptions execution options (e.g. interpolation, debug)
|
||||
* @return result of script execution and related traces
|
||||
* @throws QLException if a script or runtime error occurs
|
||||
*/
|
||||
public QLResult execute(String script, Map<String, Object> context, QLOptions qlOptions)
|
||||
throws QLException {
|
||||
return execute(script, new MapExpressContext(context), qlOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a template string by wrapping it as a dynamic string literal.
|
||||
* Template does not support newlines in this mode.
|
||||
*
|
||||
* @param template the template text to evaluate as a dynamic string
|
||||
* @param context variables available to the template
|
||||
* @param qlOptions execution options
|
||||
* @return result of template evaluation
|
||||
* @throws QLException if compilation or execution fails
|
||||
*/
|
||||
public QLResult executeTemplate(String template, Map<String, Object> context, QLOptions qlOptions)
|
||||
throws QLException {
|
||||
String script = wrapAsDynamicString(template);
|
||||
return execute(script, context, qlOptions);
|
||||
}
|
||||
|
||||
private String wrapAsDynamicString(String template) {
|
||||
if (template == null) {
|
||||
return "\"\"";
|
||||
}
|
||||
String escaped = template.replace("\"", "\\\"");
|
||||
return "\"" + escaped + "\"";
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the script with variables resolved from the fields of the given context object.
|
||||
* The variable name in the script corresponds to the field name on the object.
|
||||
*
|
||||
* @param script the script content to execute
|
||||
* @param context the object whose public fields/properties are exposed as variables
|
||||
* @param qlOptions execution options
|
||||
* @return result of script execution
|
||||
* @throws QLException if a script or runtime error occurs
|
||||
*/
|
||||
public QLResult execute(String script, Object context, QLOptions qlOptions)
|
||||
throws QLException {
|
||||
return execute(script, new ObjectFieldExpressContext(context, this), qlOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the script using objects annotated with {@code @QLAlias}.
|
||||
* The {@code QLAlias.value} serves as the variable name for each object.
|
||||
* Objects without the annotation are ignored.
|
||||
*
|
||||
* @param script the script content to execute
|
||||
* @param qlOptions execution options
|
||||
* @param objects objects annotated with {@code @QLAlias}
|
||||
* @return result of script execution
|
||||
* @throws QLException if a script or runtime error occurs
|
||||
*/
|
||||
public QLResult executeWithAliasObjects(String script, QLOptions qlOptions, Object... objects) {
|
||||
return execute(script, new QLAliasContext(objects), qlOptions);
|
||||
}
|
||||
|
||||
public QLResult execute(String script, ExpressContext context, QLOptions qlOptions) {
|
||||
QLambdaTrace mainLambdaTrace;
|
||||
if (initOptions.isDebug()) {
|
||||
long start = System.currentTimeMillis();
|
||||
mainLambdaTrace = parseToLambda(script, context, qlOptions);
|
||||
initOptions.getDebugInfoConsumer()
|
||||
.accept("Compile consume time: " + (System.currentTimeMillis() - start) + " ms");
|
||||
}
|
||||
else {
|
||||
mainLambdaTrace = parseToLambda(script, context, qlOptions);
|
||||
}
|
||||
QLambda mainLambda = mainLambdaTrace.getqLambda();
|
||||
try {
|
||||
Object result;
|
||||
if (initOptions.isDebug()) {
|
||||
long start = System.currentTimeMillis();
|
||||
result = mainLambda.call().getResult().get();
|
||||
initOptions.getDebugInfoConsumer()
|
||||
.accept("Execute consume time: " + (System.currentTimeMillis() - start) + " ms");
|
||||
}
|
||||
else {
|
||||
result = mainLambda.call().getResult().get();
|
||||
}
|
||||
|
||||
return new QLResult(result, mainLambdaTrace.getTraces().getExpressionTraces());
|
||||
}
|
||||
catch (QLException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable nuKnown) {
|
||||
// should not run here
|
||||
throw new RuntimeException(nuKnown);
|
||||
}
|
||||
}
|
||||
|
||||
private QTraces convertPoints2QTraces(List<TracePointTree> expressionTracePoints) {
|
||||
Map<Integer, ExpressionTrace> traceMap = new HashMap<>();
|
||||
List<ExpressionTrace> expressionTraces = expressionTracePoints.stream()
|
||||
.map(tracePoint -> convertPoint2Trace(tracePoint, traceMap))
|
||||
.collect(Collectors.toList());
|
||||
return new QTraces(expressionTraces, traceMap);
|
||||
}
|
||||
|
||||
private ExpressionTrace convertPoint2Trace(TracePointTree tree, Map<Integer, ExpressionTrace> traceMap) {
|
||||
if (tree.getChildren().isEmpty()) {
|
||||
ExpressionTrace result = new ExpressionTrace(tree.getType(), tree.getToken(), Collections.emptyList(),
|
||||
tree.getLine(), tree.getCol(), tree.getPosition());
|
||||
traceMap.put(result.getPosition(), result);
|
||||
return result;
|
||||
}
|
||||
List<ExpressionTrace> mergedChildren =
|
||||
tree.getChildren().stream().map(child -> convertPoint2Trace(child, traceMap)).collect(Collectors.toList());
|
||||
ExpressionTrace result = new ExpressionTrace(tree.getType(), tree.getToken(), mergedChildren, tree.getLine(),
|
||||
tree.getCol(), tree.getPosition());
|
||||
traceMap.put(result.getPosition(), result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get external variables (those that must be provided via context) referenced by the script.
|
||||
*
|
||||
* @param script the script content
|
||||
* @return names of external variables referenced in the script
|
||||
*/
|
||||
public Set<String> getOutVarNames(String script) {
|
||||
QLParser.ProgramContext programContext = parseToSyntaxTree(script);
|
||||
OutVarNamesVisitor outVarNamesVisitor = new OutVarNamesVisitor(inheritDefaultImport());
|
||||
programContext.accept(outVarNamesVisitor);
|
||||
return outVarNamesVisitor.getOutVars();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get external variable attribute access paths referenced by the script.
|
||||
*
|
||||
* @param script the script content
|
||||
* @return attribute chains accessed on external variables
|
||||
*/
|
||||
public Set<List<String>> getOutVarAttrs(String script) {
|
||||
QLParser.ProgramContext programContext = parseToSyntaxTree(script);
|
||||
OutVarAttrsVisitor outVarAttrsVisitor = new OutVarAttrsVisitor(inheritDefaultImport());
|
||||
programContext.accept(outVarAttrsVisitor);
|
||||
return outVarAttrsVisitor.getOutVarAttrs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get external functions (those that must be provided via context) referenced by the script.
|
||||
*
|
||||
* @param script the script content
|
||||
* @return names of external functions referenced in the script
|
||||
*/
|
||||
public Set<String> getOutFunctions(String script) {
|
||||
QLParser.ProgramContext programContext = parseToSyntaxTree(script);
|
||||
OutFunctionVisitor outFunctionVisitor = new OutFunctionVisitor();
|
||||
programContext.accept(outFunctionVisitor);
|
||||
return outFunctionVisitor.getOutFunctions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the expression trace trees for the script without executing it.
|
||||
*
|
||||
* @param script the script content
|
||||
* @return trace trees for each expression
|
||||
*/
|
||||
public List<TracePointTree> getExpressionTracePoints(String script) {
|
||||
QLParser.ProgramContext programContext = parseToSyntaxTree(script);
|
||||
TraceExpressionVisitor traceExpressionVisitor = new TraceExpressionVisitor();
|
||||
programContext.accept(traceExpressionVisitor);
|
||||
return traceExpressionVisitor.getExpressionTracePoints();
|
||||
}
|
||||
|
||||
/**
|
||||
* add user defined global macro to QLExpress engine
|
||||
* @param name macro name
|
||||
* @param macroScript script for macro
|
||||
* @return true if add macro successfully. fail if macro name already exists.
|
||||
*/
|
||||
public boolean addMacro(String name, String macroScript) {
|
||||
return globalScope.defineMacroIfAbsent(name, parseMacroDefine(name, macroScript));
|
||||
}
|
||||
|
||||
/**
|
||||
* add or replace user defined global macro to QLExpress engine
|
||||
* @param name macro name
|
||||
* @param macroScript script for macro
|
||||
*/
|
||||
public void addOrReplaceMacro(String name, String macroScript) {
|
||||
globalScope.defineMacro(name, parseMacroDefine(name, macroScript));
|
||||
}
|
||||
|
||||
private MacroDefine parseMacroDefine(String name, String macroScript) {
|
||||
QLParser.ProgramContext macroProgram = parseToSyntaxTree(macroScript);
|
||||
QvmInstructionVisitor macroVisitor = new QvmInstructionVisitor(macroScript, inheritDefaultImport(),
|
||||
new GeneratorScope("MACRO_" + name, globalScope), operatorManager, QvmInstructionVisitor.Context.MACRO,
|
||||
compileTimeFunctions, initOptions);
|
||||
macroProgram.accept(macroVisitor);
|
||||
List<QLInstruction> macroInstructions = macroVisitor.getInstructions();
|
||||
List<QLParser.BlockStatementContext> blockStatementContexts = macroProgram.blockStatements().blockStatement();
|
||||
boolean lastStmtExpress = !blockStatementContexts.isEmpty() && blockStatementContexts
|
||||
.get(blockStatementContexts.size() - 1) instanceof QLParser.ExpressionStatementContext;
|
||||
return new MacroDefine(macroInstructions, lastStmtExpress);
|
||||
}
|
||||
|
||||
/**
|
||||
* add user defined function to QLExpress engine
|
||||
* @param name function name
|
||||
* @param function function definition
|
||||
* @return true if add function successfully. fail if function name already exists.
|
||||
*/
|
||||
public boolean addFunction(String name, CustomFunction function) {
|
||||
CustomFunction preFunction = userDefineFunction.putIfAbsent(name, function);
|
||||
return preFunction == null;
|
||||
}
|
||||
|
||||
public <T, R> boolean addFunction(String name, Function<T, R> function) {
|
||||
return addFunction(name, (qContext, parameters) -> {
|
||||
T t = parameters.size() > 0 ? (T)parameters.get(0).get() : null;
|
||||
return function.apply(t);
|
||||
});
|
||||
}
|
||||
|
||||
public boolean addVarArgsFunction(String name, QLFunctionalVarargs functionalVarargs) {
|
||||
return addFunction(name, (qContext, parameters) -> {
|
||||
Object[] paramArr = new Object[parameters.size()];
|
||||
for (int i = 0; i < paramArr.length; i++) {
|
||||
paramArr[i] = parameters.get(i).get();
|
||||
}
|
||||
return functionalVarargs.call(paramArr);
|
||||
});
|
||||
}
|
||||
|
||||
public <T> boolean addFunction(String name, Predicate<T> predicate) {
|
||||
return addFunction(name, (qContext, parameters) -> {
|
||||
T t = parameters.size() > 0 ? (T)parameters.get(0).get() : null;
|
||||
return predicate.test(t);
|
||||
});
|
||||
}
|
||||
|
||||
public boolean addFunction(String name, Runnable runnable) {
|
||||
return addFunction(name, (qContext, parameters) -> {
|
||||
runnable.run();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public <T> boolean addFunction(String name, Consumer<T> consumer) {
|
||||
return addFunction(name, (qContext, parameters) -> {
|
||||
T t = parameters.size() > 0 ? (T)parameters.get(0).get() : null;
|
||||
consumer.accept(t);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a user-defined function backed by a specific Java service instance method.
|
||||
*
|
||||
* @param name function name exposed in QLExpress scripts
|
||||
* @param serviceObject target service instance, must not be {@code null}
|
||||
* @param methodName Java method name on the service instance
|
||||
* @param parameterClassTypes parameter type signature of the Java method; use an empty array for no-arg methods
|
||||
* @return true if the function was added successfully; false if a function with the same name already exists
|
||||
* @throws IllegalArgumentException if {@code serviceObject} or {@code methodName} is null, or if no matching
|
||||
* public method is found on the service type
|
||||
*/
|
||||
public boolean addFunctionOfServiceMethod(String name, Object serviceObject, String methodName,
|
||||
Class<?>[] parameterClassTypes) {
|
||||
if (serviceObject == null) {
|
||||
throw new IllegalArgumentException("serviceObject must not be null");
|
||||
}
|
||||
if (methodName == null) {
|
||||
throw new IllegalArgumentException("methodName must not be null");
|
||||
}
|
||||
|
||||
Class<?>[] parameterTypes = parameterClassTypes == null ? new Class<?>[0] : parameterClassTypes;
|
||||
Method method;
|
||||
try {
|
||||
method = serviceObject.getClass().getMethod(methodName, parameterTypes);
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
throw new IllegalArgumentException("No such public method '" + methodName + "' with parameter types "
|
||||
+ java.util.Arrays.toString(parameterTypes) + " on service object class '"
|
||||
+ serviceObject.getClass().getName() + "'", e);
|
||||
}
|
||||
|
||||
return addFunction(name, new QMethodFunction(serviceObject, method));
|
||||
}
|
||||
|
||||
/**
|
||||
* execute `scriptWithFunctionDefine` and add functions defined in script
|
||||
* @param scriptWithFunctionDefine script with function define
|
||||
* @param context context when execute script
|
||||
* @param qlOptions qlOptions when execute script
|
||||
* @return succ and fail functions. fail if function name already exists
|
||||
*/
|
||||
public BatchAddFunctionResult addFunctionsDefinedInScript(String scriptWithFunctionDefine, ExpressContext context,
|
||||
QLOptions qlOptions) {
|
||||
BatchAddFunctionResult batchResult = new BatchAddFunctionResult();
|
||||
QLambdaTrace mainLambdaTrace = parseToLambda(scriptWithFunctionDefine, context, qlOptions);
|
||||
try {
|
||||
Map<String, CustomFunction> functionTableInScript = mainLambdaTrace.getqLambda().getFunctionDefined();
|
||||
for (Map.Entry<String, CustomFunction> entry : functionTableInScript.entrySet()) {
|
||||
boolean addResult = addFunction(entry.getKey(), entry.getValue());
|
||||
(addResult ? batchResult.getSucc() : batchResult.getFail()).add(entry.getKey());
|
||||
}
|
||||
return batchResult;
|
||||
}
|
||||
catch (QLException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable e) {
|
||||
// should not run here
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add object member method with annotation {@link com.alibaba.qlexpress4.annotation.QLFunction} as function
|
||||
* @param object object with member method with annotation {@link com.alibaba.qlexpress4.annotation.QLFunction}
|
||||
* @return succ and fail functions. fail if function name already exists or method is not public
|
||||
*/
|
||||
public BatchAddFunctionResult addObjFunction(Object object) {
|
||||
return addFunctionByAnnotation(object.getClass(), object);
|
||||
}
|
||||
|
||||
/**
|
||||
* add class static method with annotation {@link com.alibaba.qlexpress4.annotation.QLFunction} as function
|
||||
* @param clazz class with static method with annotation {@link com.alibaba.qlexpress4.annotation.QLFunction}
|
||||
* @return succ and fail functions. fail if function name already exists or method is not public
|
||||
*/
|
||||
public BatchAddFunctionResult addStaticFunction(Class<?> clazz) {
|
||||
return addFunctionByAnnotation(clazz, null);
|
||||
}
|
||||
|
||||
private BatchAddFunctionResult addFunctionByAnnotation(Class<?> clazz, Object object) {
|
||||
BatchAddFunctionResult result = new BatchAddFunctionResult();
|
||||
Method[] methods = clazz.getDeclaredMethods();
|
||||
for (Method method : methods) {
|
||||
if (!BasicUtil.isPublic(method)) {
|
||||
result.getFail().add(method.getName());
|
||||
continue;
|
||||
}
|
||||
if (QLFunctionUtil.containsQLFunctionForMethod(method)) {
|
||||
for (String functionName : QLFunctionUtil.getQLFunctionValue(method)) {
|
||||
boolean addResult = addFunction(functionName, new QMethodFunction(object, method));
|
||||
(addResult ? result.getSucc() : result.getFail()).add(method.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* add compile time function
|
||||
* @param name function name
|
||||
* @param compileTimeFunction definition
|
||||
* @return true if successful
|
||||
*/
|
||||
public boolean addCompileTimeFunction(String name, CompileTimeFunction compileTimeFunction) {
|
||||
return compileTimeFunctions.putIfAbsent(name, compileTimeFunction) == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* add extension function
|
||||
* @param extensionFunction definition of extansion function
|
||||
*/
|
||||
public void addExtendFunction(ExtensionFunction extensionFunction) {
|
||||
this.reflectLoader.addExtendFunction(extensionFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* add an extension function with variable arguments.
|
||||
* @param name the name of the extension function
|
||||
* @param bindingClass the receiver type (class)
|
||||
* @param functionalVarargs custom logic
|
||||
*/
|
||||
public void addExtendFunction(String name, Class<?> bindingClass, QLFunctionalVarargs functionalVarargs) {
|
||||
this.reflectLoader.addExtendFunction(new ExtensionFunction() {
|
||||
@Override
|
||||
public Class<?>[] getParameterTypes() {
|
||||
return new Class[] {Object[].class};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVarArgs() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getDeclaringClass() {
|
||||
return bindingClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object obj, Object[] args)
|
||||
throws InvocationTargetException, IllegalAccessException {
|
||||
Object[] varArgs = (Object[])args[0];
|
||||
Object[] extArgs = new Object[varArgs.length + 1];
|
||||
extArgs[0] = obj;
|
||||
System.arraycopy(varArgs, 0, extArgs, 1, varArgs.length);
|
||||
return functionalVarargs.call(extArgs);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public QLParser.ProgramContext parseToSyntaxTree(String script) {
|
||||
return SyntaxTreeFactory.buildTree(script,
|
||||
operatorManager,
|
||||
initOptions.isDebug(),
|
||||
false,
|
||||
initOptions.getDebugInfoConsumer(),
|
||||
initOptions.getInterpolationMode(),
|
||||
initOptions.getSelectorStart(),
|
||||
initOptions.getSelectorEnd(),
|
||||
initOptions.isStrictNewLines());
|
||||
}
|
||||
|
||||
public void check(String script, CheckOptions checkOptions)
|
||||
throws QLSyntaxException {
|
||||
// 1. Parse syntax tree (reuse existing parseToSyntaxTree logic)
|
||||
QLParser.ProgramContext programContext = parseToSyntaxTree(script);
|
||||
|
||||
// 2. Create CheckVisitor and pass validation configuration and script content
|
||||
CheckVisitor checkVisitor = new CheckVisitor(checkOptions, script);
|
||||
|
||||
// 3. Traverse syntax tree and perform operator validation during traversal
|
||||
programContext.accept(checkVisitor);
|
||||
}
|
||||
|
||||
public void check(String script)
|
||||
throws QLSyntaxException {
|
||||
check(script, CheckOptions.DEFAULT_OPTIONS);
|
||||
}
|
||||
|
||||
public QLambdaTrace parseToLambda(String script, ExpressContext context, QLOptions qlOptions) {
|
||||
QCompileCache mainLambdaDefine =
|
||||
qlOptions.isCache() ? parseToDefinitionWithCache(script) : parseDefinition(script);
|
||||
if (initOptions.isDebug()) {
|
||||
initOptions.getDebugInfoConsumer().accept("\nInstructions:");
|
||||
mainLambdaDefine.getQLambdaDefinition().println(0, initOptions.getDebugInfoConsumer());
|
||||
}
|
||||
|
||||
QTraces qTraces = initOptions.isTraceExpression() && qlOptions.isTraceExpression()
|
||||
? convertPoints2QTraces(mainLambdaDefine.getExpressionTracePoints())
|
||||
: new QTraces(null, null);
|
||||
|
||||
QvmRuntime qvmRuntime =
|
||||
new QvmRuntime(qTraces, qlOptions.getAttachments(), reflectLoader, System.currentTimeMillis());
|
||||
QvmGlobalScope globalScope = new QvmGlobalScope(context, userDefineFunction, qlOptions);
|
||||
QLambda qLambda = mainLambdaDefine.getQLambdaDefinition()
|
||||
.toLambda(new DelegateQContext(qvmRuntime, globalScope), qlOptions, true);
|
||||
return new QLambdaTrace(qLambda, qTraces);
|
||||
}
|
||||
|
||||
/**
|
||||
* parse script with cache
|
||||
* @param script script to parse
|
||||
* @return QLambdaDefinition and TracePointTrees
|
||||
*/
|
||||
public QCompileCache parseToDefinitionWithCache(String script) {
|
||||
try {
|
||||
return getParseFuture(script).get();
|
||||
}
|
||||
catch (Exception e) {
|
||||
Throwable compileException = e.getCause();
|
||||
throw compileException instanceof QLSyntaxException ? (QLSyntaxException)compileException
|
||||
: new RuntimeException(compileException);
|
||||
}
|
||||
}
|
||||
|
||||
public Value loadField(Object object, String fieldName) {
|
||||
return reflectLoader.loadField(object, fieldName, true, PureErrReporter.INSTANCE);
|
||||
}
|
||||
|
||||
private static final String PARSER_CLASS_NAME = "com.alibaba.qlexpress4.aparser.QLParser";
|
||||
|
||||
private static final String DFA_FIELD_NAME = "_decisionToDFA";
|
||||
|
||||
/**
|
||||
* Clears the DFA (Deterministic Finite Automaton) cache used by the ANTLR parser.
|
||||
* <p>
|
||||
* <b>WARNING:</b> Calling this method will cause a significant compilation performance degradation.
|
||||
* It is NOT recommended for normal use cases.
|
||||
* </p>
|
||||
*
|
||||
* <h3>Use Cases:</h3>
|
||||
* <ul>
|
||||
* <li><b>Memory-sensitive applications:</b> When memory usage is a critical concern and you can
|
||||
* tolerate slower compilation times</li>
|
||||
* <li><b>Infrequently changing scripts:</b> When scripts are relatively stable and not frequently
|
||||
* recompiled</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h3>Best Practice:</h3>
|
||||
* <p>
|
||||
* Call this method immediately after parsing and caching your expression, and ensure all subsequent
|
||||
* executions use the cached version to avoid recompilation. Example:
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
* /*
|
||||
* * When the expression changes, parse it and add it to the expression cache;
|
||||
* * after parsing is complete, call clearDFACache.
|
||||
* *\/
|
||||
* Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
* runner.parseToDefinitionWithCache(complexDataProcessingExpress);
|
||||
* runner.clearDFACache();
|
||||
*
|
||||
* /*
|
||||
* * All subsequent runs of this script must enable the cache option to ensure that re-compilation does not occur.
|
||||
* *\/
|
||||
* for (int i = 0; i < 3; i++) {
|
||||
* runner.execute(complexDataProcessingExpress, Collections.emptyMap(), QLOptions
|
||||
* .builder().cache(true).build());
|
||||
* }
|
||||
* }</pre>
|
||||
*/
|
||||
public void clearDFACache() {
|
||||
DFA[] decisionToDFA = getDecisionToDFA();
|
||||
|
||||
for (int d = 0; d < decisionToDFA.length; d++) {
|
||||
decisionToDFA[d] = new DFA(QLParser._ATN.getDecisionState(d), d);
|
||||
}
|
||||
}
|
||||
|
||||
private DFA[] getDecisionToDFA() {
|
||||
try {
|
||||
Class<?> parserClass = Class.forName(PARSER_CLASS_NAME);
|
||||
Field dfaField = parserClass.getDeclaredField(DFA_FIELD_NAME);
|
||||
dfaField.setAccessible(true);
|
||||
return (DFA[])dfaField.get(null);
|
||||
}
|
||||
catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the compilation cache.
|
||||
* This method clears the cache that stores compiled scripts for performance optimization.
|
||||
* When the cache is cleared, subsequent script executions will need to recompile the scripts,
|
||||
* which may temporarily impact performance until the cache is rebuilt.
|
||||
*/
|
||||
public void clearCompileCache() {
|
||||
compileCache.clear();
|
||||
}
|
||||
|
||||
private Future<QCompileCache> getParseFuture(String script) {
|
||||
Future<QCompileCache> parseFuture = compileCache.get(script);
|
||||
if (parseFuture != null) {
|
||||
return parseFuture;
|
||||
}
|
||||
FutureTask<QCompileCache> parseTask = new FutureTask<>(() -> parseDefinition(script));
|
||||
Future<QCompileCache> preTask = compileCache.putIfAbsent(script, parseTask);
|
||||
if (preTask == null) {
|
||||
parseTask.run();
|
||||
return parseTask;
|
||||
}
|
||||
return preTask;
|
||||
}
|
||||
|
||||
private QCompileCache parseDefinition(String script) {
|
||||
QLParser.ProgramContext program = parseToSyntaxTree(script);
|
||||
QvmInstructionVisitor qvmInstructionVisitor = new QvmInstructionVisitor(script, inheritDefaultImport(),
|
||||
globalScope, operatorManager, compileTimeFunctions, initOptions);
|
||||
program.accept(qvmInstructionVisitor);
|
||||
|
||||
QLambdaDefinitionInner qLambdaDefinition = new QLambdaDefinitionInner("main",
|
||||
qvmInstructionVisitor.getInstructions(), Collections.emptyList(), qvmInstructionVisitor.getMaxStackSize());
|
||||
if (initOptions.isTraceExpression()) {
|
||||
TraceExpressionVisitor traceExpressionVisitor = new TraceExpressionVisitor();
|
||||
program.accept(traceExpressionVisitor);
|
||||
List<TracePointTree> tracePoints = traceExpressionVisitor.getExpressionTracePoints();
|
||||
return new QCompileCache(qLambdaDefinition, tracePoints);
|
||||
}
|
||||
else {
|
||||
return new QCompileCache(qLambdaDefinition, Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
private ImportManager inheritDefaultImport() {
|
||||
return new ImportManager(initOptions.getClassSupplier(), initOptions.getDefaultImport());
|
||||
}
|
||||
|
||||
public <T, U, R> boolean addOperatorBiFunction(String operator, BiFunction<T, U, R> biFunction) {
|
||||
return operatorManager.addBinaryOperator(operator,
|
||||
(left, right) -> biFunction.apply((T)left.get(), (U)right.get()),
|
||||
QLPrecedences.MULTI);
|
||||
}
|
||||
|
||||
public boolean addOperator(String operator, QLFunctionalVarargs functionalVarargs) {
|
||||
return addOperator(operator, (left, right) -> functionalVarargs.call(left.get(), right.get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* add operator with multi precedences
|
||||
* @param operator operator name
|
||||
* @param customBinaryOperator operator implement
|
||||
* @return true if add operator successfully; false if operator already exist
|
||||
*/
|
||||
public boolean addOperator(String operator, CustomBinaryOperator customBinaryOperator) {
|
||||
return operatorManager.addBinaryOperator(operator, customBinaryOperator, QLPrecedences.MULTI);
|
||||
}
|
||||
|
||||
/**
|
||||
* add operator
|
||||
* @param operator operator name
|
||||
* @param customBinaryOperator operator implement
|
||||
* @param precedence precedence, see {@link QLPrecedences}
|
||||
* @return true if add operator successfully; false if operator already exist
|
||||
*/
|
||||
public boolean addOperator(String operator, CustomBinaryOperator customBinaryOperator, int precedence) {
|
||||
return operatorManager.addBinaryOperator(operator, customBinaryOperator, precedence);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param operator operator name
|
||||
* @param customBinaryOperator operator implement
|
||||
* @return true if replace operator successfully; false if default operator not exists
|
||||
*/
|
||||
public boolean replaceDefaultOperator(String operator, CustomBinaryOperator customBinaryOperator) {
|
||||
return operatorManager.replaceDefaultOperator(operator, customBinaryOperator);
|
||||
}
|
||||
|
||||
/**
|
||||
* add alias for keyWord, operator and function
|
||||
* @param alias must be a valid id
|
||||
* @param originToken key word in qlexpress
|
||||
* @return true if add alias successfully
|
||||
*/
|
||||
public boolean addAlias(String alias, String originToken) {
|
||||
boolean addKeyWordAliasResult = operatorManager.addKeyWordAlias(alias, originToken);
|
||||
boolean addOperatorAliasResult = operatorManager.addOperatorAlias(alias, originToken);
|
||||
boolean addFunctionAliasResult = addFunctionAlias(alias, originToken);
|
||||
|
||||
return addKeyWordAliasResult || addOperatorAliasResult || addFunctionAliasResult;
|
||||
}
|
||||
|
||||
private boolean addFunctionAlias(String alias, String originToken) {
|
||||
CustomFunction customFunction = userDefineFunction.get(originToken);
|
||||
if (customFunction != null) {
|
||||
return userDefineFunction.putIfAbsent(alias, customFunction) == null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
245
src/main/java/com/alibaba/qlexpress4/InitOptions.java
Normal file
245
src/main/java/com/alibaba/qlexpress4/InitOptions.java
Normal file
@@ -0,0 +1,245 @@
|
||||
package com.alibaba.qlexpress4;
|
||||
|
||||
import com.alibaba.qlexpress4.aparser.ImportManager;
|
||||
import com.alibaba.qlexpress4.aparser.InterpolationMode;
|
||||
import com.alibaba.qlexpress4.security.QLSecurityStrategy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Author: TaoKan
|
||||
*/
|
||||
public class InitOptions {
|
||||
|
||||
public static InitOptions DEFAULT_OPTIONS = InitOptions.builder().build();
|
||||
|
||||
private final ClassSupplier classSupplier;
|
||||
|
||||
/**
|
||||
* default import java packages for script
|
||||
* default
|
||||
* ImportManager.importPack("java.lang"),
|
||||
* ImportManager.importPack("java.util"),
|
||||
* ImportManager.importPack("java.math"),
|
||||
* ImportManager.importPack("java.util.stream")
|
||||
* ImportManager.importPack("java.util.function")
|
||||
*/
|
||||
private final List<ImportManager.QLImport> defaultImport;
|
||||
|
||||
/**
|
||||
* enable debug mode
|
||||
* default false
|
||||
*/
|
||||
private final boolean debug;
|
||||
|
||||
/**
|
||||
* consume all debug info, valid when debug is true
|
||||
* default is print in standard output, can not be null
|
||||
*/
|
||||
private final Consumer<String> debugInfoConsumer;
|
||||
|
||||
/**
|
||||
* qlexpress security strategy
|
||||
* default is isolation, not allow any access to java
|
||||
*/
|
||||
private final QLSecurityStrategy securityStrategy;
|
||||
|
||||
/**
|
||||
* allow access private field and method
|
||||
* default false
|
||||
*/
|
||||
private final boolean allowPrivateAccess;
|
||||
|
||||
/**
|
||||
* How to manage string interpolation, for instance, "a ${t-c} b"
|
||||
* default SCRIPT
|
||||
*/
|
||||
private final InterpolationMode interpolationMode;
|
||||
|
||||
/**
|
||||
* track the execution process of all expressions and return the path to the `execute` caller.
|
||||
* default false
|
||||
*/
|
||||
private final boolean traceExpression;
|
||||
|
||||
/**
|
||||
* selector start token, must be one of: "${", "$[", "#{", or "#[".
|
||||
* default is "${".
|
||||
*/
|
||||
private final String selectorStart;
|
||||
|
||||
/**
|
||||
* selector end token, must be 1 or more characters
|
||||
* default is "}"
|
||||
*/
|
||||
private final String selectorEnd;
|
||||
|
||||
/**
|
||||
* Strictly require a line break between any two statements (since semicolons can be omitted in QLExpress4).
|
||||
* default is true
|
||||
*/
|
||||
private final boolean strictNewLines;
|
||||
|
||||
private InitOptions(ClassSupplier classSupplier, List<ImportManager.QLImport> defaultImport, boolean debug,
|
||||
Consumer<String> debugInfoConsumer, QLSecurityStrategy securityStrategy, boolean allowPrivateAccess,
|
||||
InterpolationMode interpolationMode, boolean traceExpression, String selectorStart, String selectorEnd,
|
||||
boolean strictNewLines) {
|
||||
this.classSupplier = classSupplier;
|
||||
this.defaultImport = defaultImport;
|
||||
this.debug = debug;
|
||||
this.debugInfoConsumer = debugInfoConsumer;
|
||||
this.securityStrategy = securityStrategy;
|
||||
this.allowPrivateAccess = allowPrivateAccess;
|
||||
this.interpolationMode = interpolationMode;
|
||||
this.traceExpression = traceExpression;
|
||||
this.selectorStart = selectorStart;
|
||||
this.selectorEnd = selectorEnd;
|
||||
this.strictNewLines = strictNewLines;
|
||||
}
|
||||
|
||||
public static InitOptions.Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public List<ImportManager.QLImport> getDefaultImport() {
|
||||
return defaultImport;
|
||||
}
|
||||
|
||||
public ClassSupplier getClassSupplier() {
|
||||
return classSupplier;
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
return debug;
|
||||
}
|
||||
|
||||
public Consumer<String> getDebugInfoConsumer() {
|
||||
return debugInfoConsumer;
|
||||
}
|
||||
|
||||
public QLSecurityStrategy getSecurityStrategy() {
|
||||
return securityStrategy;
|
||||
}
|
||||
|
||||
public boolean isAllowPrivateAccess() {
|
||||
return allowPrivateAccess;
|
||||
}
|
||||
|
||||
public InterpolationMode getInterpolationMode() {
|
||||
return interpolationMode;
|
||||
}
|
||||
|
||||
public boolean isTraceExpression() {
|
||||
return traceExpression;
|
||||
}
|
||||
|
||||
public String getSelectorStart() {
|
||||
return selectorStart;
|
||||
}
|
||||
|
||||
public String getSelectorEnd() {
|
||||
return selectorEnd;
|
||||
}
|
||||
|
||||
public boolean isStrictNewLines() {
|
||||
return strictNewLines;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private ClassSupplier classSupplier = DefaultClassSupplier.getInstance();
|
||||
|
||||
private final List<ImportManager.QLImport> defaultImport =
|
||||
new ArrayList<>(Arrays.asList(ImportManager.importPack("java.lang"),
|
||||
ImportManager.importPack("java.util"),
|
||||
ImportManager.importPack("java.math"),
|
||||
ImportManager.importPack("java.util.stream"),
|
||||
ImportManager.importPack("java.util.function")));
|
||||
|
||||
private boolean debug = false;
|
||||
|
||||
private Consumer<String> debugInfoConsumer = System.out::println;
|
||||
|
||||
private QLSecurityStrategy securityStrategy = QLSecurityStrategy.isolation();
|
||||
|
||||
private boolean allowPrivateAccess = false;
|
||||
|
||||
private InterpolationMode interpolationMode = InterpolationMode.SCRIPT;
|
||||
|
||||
private boolean traceExpression = false;
|
||||
|
||||
private String selectorStart = "${";
|
||||
|
||||
private String selectorEnd = "}";
|
||||
|
||||
private boolean strictNewLines = true;
|
||||
|
||||
public Builder classSupplier(ClassSupplier classSupplier) {
|
||||
this.classSupplier = classSupplier;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addDefaultImport(List<ImportManager.QLImport> defaultImport) {
|
||||
this.defaultImport.addAll(defaultImport);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder debug(boolean debug) {
|
||||
this.debug = debug;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder debugInfoConsumer(Consumer<String> debugInfoConsumer) {
|
||||
this.debugInfoConsumer = debugInfoConsumer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder securityStrategy(QLSecurityStrategy securityStrategy) {
|
||||
this.securityStrategy = securityStrategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder allowPrivateAccess(boolean allowPrivateAccess) {
|
||||
this.allowPrivateAccess = allowPrivateAccess;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder interpolationMode(InterpolationMode interpolationMode) {
|
||||
this.interpolationMode = interpolationMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder traceExpression(boolean traceExpression) {
|
||||
this.traceExpression = traceExpression;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder selectorStart(String selectorStart) {
|
||||
if (!Arrays.asList("${", "$[", "#{", "#[").contains(selectorStart)) {
|
||||
throw new IllegalArgumentException("Custom selector start must in '${' | '$[' | '#{' | '#['");
|
||||
}
|
||||
this.selectorStart = selectorStart;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder selectorEnd(String selectorEnd) {
|
||||
if (selectorEnd == null || selectorEnd.isEmpty()) {
|
||||
throw new IllegalArgumentException("Custom selector end must be 1 or more characters");
|
||||
}
|
||||
this.selectorEnd = selectorEnd;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder strictNewLines(boolean strictNewLines) {
|
||||
this.strictNewLines = strictNewLines;
|
||||
return this;
|
||||
}
|
||||
|
||||
public InitOptions build() {
|
||||
return new InitOptions(classSupplier, defaultImport, debug, debugInfoConsumer, securityStrategy,
|
||||
allowPrivateAccess, interpolationMode, traceExpression, selectorStart, selectorEnd, strictNewLines);
|
||||
}
|
||||
}
|
||||
}
|
||||
205
src/main/java/com/alibaba/qlexpress4/QLOptions.java
Normal file
205
src/main/java/com/alibaba/qlexpress4/QLOptions.java
Normal file
@@ -0,0 +1,205 @@
|
||||
package com.alibaba.qlexpress4;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
* date 2022/1/12 2:35 下午
|
||||
*/
|
||||
public class QLOptions {
|
||||
|
||||
public static QLOptions DEFAULT_OPTIONS = QLOptions.builder().build();
|
||||
|
||||
/**
|
||||
* precise evaluate based on BigDecimal
|
||||
* default false
|
||||
*/
|
||||
private final boolean precise;
|
||||
|
||||
/**
|
||||
* define global symbol in user context
|
||||
* default false
|
||||
*/
|
||||
private final boolean polluteUserContext;
|
||||
|
||||
/**
|
||||
* script timeout millisecond, default is -1, namely time unlimited
|
||||
* <= 0, time unlimited
|
||||
* default -1
|
||||
*/
|
||||
private final long timeoutMillis;
|
||||
|
||||
/**
|
||||
* attachments will be carried to user defined function/operator/macro
|
||||
* only used to pass data, not as variable value
|
||||
*
|
||||
* default empty map
|
||||
*/
|
||||
private final Map<String, Object> attachments;
|
||||
|
||||
/**
|
||||
* allow cache compile result of script
|
||||
*
|
||||
* default false
|
||||
*/
|
||||
private final boolean cache;
|
||||
|
||||
/**
|
||||
* avoid null pointer
|
||||
* default false
|
||||
*/
|
||||
private final boolean avoidNullPointer;
|
||||
|
||||
/**
|
||||
* max length of arrays allowed to be created
|
||||
* -1 means no limit
|
||||
* default -1
|
||||
*/
|
||||
private final int maxArrLength;
|
||||
|
||||
/**
|
||||
* Track the execution process of all expressions and return the path to the `execute` caller.
|
||||
* To enable expression tracing, please ensure that the InitOptions.traceExpression is alse set to true.
|
||||
* default false
|
||||
*/
|
||||
private final boolean traceExpression;
|
||||
|
||||
/**
|
||||
* disable short circuit in logic operator
|
||||
* default false
|
||||
*/
|
||||
private final boolean shortCircuitDisable;
|
||||
|
||||
private QLOptions(boolean precise, boolean polluteUserContext, long timeoutMillis, Map<String, Object> attachments,
|
||||
boolean cache, boolean avoidNullPointer, int maxArrLength, boolean traceExpression,
|
||||
boolean shortCircuitDisable) {
|
||||
this.precise = precise;
|
||||
this.polluteUserContext = polluteUserContext;
|
||||
this.timeoutMillis = timeoutMillis;
|
||||
this.attachments = attachments;
|
||||
this.cache = cache;
|
||||
this.avoidNullPointer = avoidNullPointer;
|
||||
this.maxArrLength = maxArrLength;
|
||||
this.traceExpression = traceExpression;
|
||||
this.shortCircuitDisable = shortCircuitDisable;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public boolean isPrecise() {
|
||||
return precise;
|
||||
}
|
||||
|
||||
public boolean isPolluteUserContext() {
|
||||
return polluteUserContext;
|
||||
}
|
||||
|
||||
public long getTimeoutMillis() {
|
||||
return timeoutMillis;
|
||||
}
|
||||
|
||||
public Map<String, Object> getAttachments() {
|
||||
return attachments;
|
||||
}
|
||||
|
||||
public boolean isCache() {
|
||||
return cache;
|
||||
}
|
||||
|
||||
public boolean isAvoidNullPointer() {
|
||||
return avoidNullPointer;
|
||||
}
|
||||
|
||||
public int getMaxArrLength() {
|
||||
return maxArrLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newArrLen new arr length in runtime
|
||||
* @return true if less or equal to max arr len
|
||||
*/
|
||||
public boolean checkArrLen(int newArrLen) {
|
||||
return maxArrLength == -1 || newArrLen <= maxArrLength;
|
||||
}
|
||||
|
||||
public boolean isTraceExpression() {
|
||||
return traceExpression;
|
||||
}
|
||||
|
||||
public boolean isShortCircuitDisable() {
|
||||
return shortCircuitDisable;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private boolean precise = false;
|
||||
|
||||
private boolean polluteUserContext = false;
|
||||
|
||||
private long timeoutMillis = -1;
|
||||
|
||||
private Map<String, Object> attachments = Collections.emptyMap();
|
||||
|
||||
private boolean cache = false;
|
||||
|
||||
private boolean avoidNullPointer = false;
|
||||
|
||||
private int maxArrLength = -1;
|
||||
|
||||
private boolean traceExpression = false;
|
||||
|
||||
private boolean shortCircuitDisable = false;
|
||||
|
||||
public Builder precise(boolean precise) {
|
||||
this.precise = precise;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder polluteUserContext(boolean polluteUserContext) {
|
||||
this.polluteUserContext = polluteUserContext;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder timeoutMillis(long timeoutMillis) {
|
||||
this.timeoutMillis = timeoutMillis;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder attachments(Map<String, Object> attachments) {
|
||||
this.attachments = attachments;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder cache(boolean cache) {
|
||||
this.cache = cache;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder avoidNullPointer(boolean avoidNullPointer) {
|
||||
this.avoidNullPointer = avoidNullPointer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder maxArrLength(int maxArrLength) {
|
||||
this.maxArrLength = maxArrLength;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder traceExpression(boolean traceExpression) {
|
||||
this.traceExpression = traceExpression;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder shortCircuitDisable(boolean shortCircuitDisable) {
|
||||
this.shortCircuitDisable = shortCircuitDisable;
|
||||
return this;
|
||||
}
|
||||
|
||||
public QLOptions build() {
|
||||
return new QLOptions(precise, polluteUserContext, timeoutMillis, attachments, cache, avoidNullPointer,
|
||||
maxArrLength, traceExpression, shortCircuitDisable);
|
||||
}
|
||||
}
|
||||
}
|
||||
89
src/main/java/com/alibaba/qlexpress4/QLPrecedences.java
Normal file
89
src/main/java/com/alibaba/qlexpress4/QLPrecedences.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package com.alibaba.qlexpress4;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
* date 2022/1/12 2:31 下午
|
||||
*/
|
||||
public class QLPrecedences {
|
||||
|
||||
/*
|
||||
* = += -= &= |= *= /= %= <<= >>=
|
||||
*/
|
||||
public static final int ASSIGN = 0;
|
||||
|
||||
/*
|
||||
* ?:
|
||||
*/
|
||||
public static final int TERNARY = 1;
|
||||
|
||||
/*
|
||||
* || or
|
||||
*/
|
||||
public static final int OR = 2;
|
||||
|
||||
/*
|
||||
* && and
|
||||
*/
|
||||
public static final int AND = 3;
|
||||
|
||||
/*
|
||||
* |
|
||||
*/
|
||||
public static final int BIT_OR = 4;
|
||||
|
||||
/*
|
||||
* ^
|
||||
*/
|
||||
public static final int XOR = 5;
|
||||
|
||||
/*
|
||||
* &
|
||||
*/
|
||||
public static final int BIT_AND = 6;
|
||||
|
||||
/*
|
||||
* == !=
|
||||
*/
|
||||
public static final int EQUAL = 7;
|
||||
|
||||
/*
|
||||
* < <= > >= instanceof
|
||||
*/
|
||||
public static final int COMPARE = 8;
|
||||
|
||||
/*
|
||||
* << >> >>>
|
||||
*/
|
||||
public static final int BIT_MOVE = 9;
|
||||
|
||||
/*
|
||||
* in like
|
||||
*/
|
||||
public static final int IN_LIKE = 10;
|
||||
|
||||
/*
|
||||
* + -
|
||||
*/
|
||||
public static final int ADD = 11;
|
||||
|
||||
/*
|
||||
* * / %
|
||||
*/
|
||||
public static final int MULTI = 12;
|
||||
|
||||
/*
|
||||
* ! ++ -- ~ + -
|
||||
*/
|
||||
public static final int UNARY = 13;
|
||||
|
||||
/*
|
||||
* ++ -- in suffix, like i++
|
||||
*/
|
||||
public static final int UNARY_SUFFIX = 14;
|
||||
|
||||
/*
|
||||
* . ()
|
||||
*/
|
||||
public static final int GROUP = 15;
|
||||
|
||||
}
|
||||
25
src/main/java/com/alibaba/qlexpress4/QLResult.java
Normal file
25
src/main/java/com/alibaba/qlexpress4/QLResult.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package com.alibaba.qlexpress4;
|
||||
|
||||
import com.alibaba.qlexpress4.runtime.trace.ExpressionTrace;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class QLResult {
|
||||
|
||||
private final Object result;
|
||||
|
||||
private final List<ExpressionTrace> expressionTraces;
|
||||
|
||||
public QLResult(Object result, List<ExpressionTrace> expressionTraces) {
|
||||
this.result = result;
|
||||
this.expressionTraces = expressionTraces;
|
||||
}
|
||||
|
||||
public Object getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<ExpressionTrace> getExpressionTraces() {
|
||||
return expressionTraces;
|
||||
}
|
||||
}
|
||||
15
src/main/java/com/alibaba/qlexpress4/annotation/QLAlias.java
Normal file
15
src/main/java/com/alibaba/qlexpress4/annotation/QLAlias.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.alibaba.qlexpress4.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
import static java.lang.annotation.ElementType.*;
|
||||
|
||||
@Inherited
|
||||
@Target({TYPE, FIELD, METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface QLAlias {
|
||||
/**
|
||||
* @return aliases
|
||||
*/
|
||||
String[] value();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.alibaba.qlexpress4.annotation;
|
||||
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
|
||||
/**
|
||||
* Author: TaoKan
|
||||
*/
|
||||
@Inherited
|
||||
@Target({METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface QLFunction {
|
||||
/**
|
||||
* @return function names
|
||||
*/
|
||||
String[] value();
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
import org.antlr.v4.runtime.CharStream;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.TokenFactory;
|
||||
import org.antlr.v4.runtime.TokenSource;
|
||||
|
||||
public class AliasTokenSource implements TokenSource {
|
||||
|
||||
private final TokenSource tokenSource;
|
||||
|
||||
private final ParserOperatorManager operatorManager;
|
||||
|
||||
AliasTokenSource(TokenSource tokenSource, ParserOperatorManager operatorManager) {
|
||||
this.tokenSource = tokenSource;
|
||||
this.operatorManager = operatorManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Token nextToken() {
|
||||
return SyntaxTreeFactory.preHandleToken(tokenSource.nextToken(), operatorManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLine() {
|
||||
return tokenSource.getLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCharPositionInLine() {
|
||||
return tokenSource.getCharPositionInLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharStream getInputStream() {
|
||||
return tokenSource.getInputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSourceName() {
|
||||
return tokenSource.getSourceName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTokenFactory(TokenFactory<?> factory) {
|
||||
tokenSource.setTokenFactory(factory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TokenFactory<?> getTokenFactory() {
|
||||
return tokenSource.getTokenFactory();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
import org.antlr.v4.runtime.BufferedTokenStream;
|
||||
import org.antlr.v4.runtime.RuleContext;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.TokenSource;
|
||||
import org.antlr.v4.runtime.TokenStream;
|
||||
import org.antlr.v4.runtime.misc.Interval;
|
||||
|
||||
public class AliasTokenStream implements TokenStream {
|
||||
|
||||
private final BufferedTokenStream stream;
|
||||
|
||||
private final ParserOperatorManager operatorManager;
|
||||
|
||||
public AliasTokenStream(BufferedTokenStream stream, ParserOperatorManager operatorManager) {
|
||||
this.stream = stream;
|
||||
this.operatorManager = operatorManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Token LT(int k) {
|
||||
return SyntaxTreeFactory.preHandleToken(stream.LT(k), operatorManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Token get(int index) {
|
||||
return SyntaxTreeFactory.preHandleToken(stream.get(index), operatorManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TokenSource getTokenSource() {
|
||||
return new AliasTokenSource(stream.getTokenSource(), operatorManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText(Interval interval) {
|
||||
return stream.getText(interval);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return stream.getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText(RuleContext ctx) {
|
||||
return stream.getText(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText(Token start, Token stop) {
|
||||
return stream.getText(start, stop);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume() {
|
||||
stream.consume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int LA(int i) {
|
||||
return SyntaxTreeFactory.preHandleToken(LT(i), operatorManager).getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int mark() {
|
||||
return stream.mark();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release(int marker) {
|
||||
stream.release(marker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int index() {
|
||||
return stream.index();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(int index) {
|
||||
stream.seek(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return stream.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSourceName() {
|
||||
return stream.getSourceName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
public class BuiltInTypesSet {
|
||||
|
||||
public static final String BYTE = "byte";
|
||||
|
||||
public static final String SHORT = "short";
|
||||
|
||||
public static final String INT = "int";
|
||||
|
||||
public static final String LONG = "long";
|
||||
|
||||
public static final String FLOAT = "float";
|
||||
|
||||
public static final String DOUBLE = "double";
|
||||
|
||||
public static final String BOOLEAN = "boolean";
|
||||
|
||||
public static final String CHAR = "char";
|
||||
|
||||
public static Class<?> getCls(String lexeme) {
|
||||
switch (lexeme) {
|
||||
case BYTE:
|
||||
return Byte.class;
|
||||
case SHORT:
|
||||
return Short.class;
|
||||
case INT:
|
||||
return Integer.class;
|
||||
case LONG:
|
||||
return Long.class;
|
||||
case FLOAT:
|
||||
return Float.class;
|
||||
case DOUBLE:
|
||||
return Double.class;
|
||||
case BOOLEAN:
|
||||
return Boolean.class;
|
||||
case CHAR:
|
||||
return Character.class;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
142
src/main/java/com/alibaba/qlexpress4/aparser/CheckVisitor.java
Normal file
142
src/main/java/com/alibaba/qlexpress4/aparser/CheckVisitor.java
Normal file
@@ -0,0 +1,142 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
import com.alibaba.qlexpress4.CheckOptions;
|
||||
import com.alibaba.qlexpress4.exception.QLErrorCodes;
|
||||
import com.alibaba.qlexpress4.exception.QLException;
|
||||
import com.alibaba.qlexpress4.exception.QLSyntaxException;
|
||||
import com.alibaba.qlexpress4.operator.OperatorCheckStrategy;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
/**
|
||||
* @author zhoutao
|
||||
*/
|
||||
public class CheckVisitor extends QLParserBaseVisitor<Void> {
|
||||
|
||||
/**
|
||||
* Operator restriction strategy
|
||||
*/
|
||||
private final OperatorCheckStrategy operatorCheckStrategy;
|
||||
|
||||
/**
|
||||
* Whether to disable function calls
|
||||
*/
|
||||
private final boolean disableFunctionCalls;
|
||||
|
||||
/**
|
||||
* Script content for error reporting
|
||||
*/
|
||||
private final String script;
|
||||
|
||||
public CheckVisitor(CheckOptions checkOptions) {
|
||||
this(checkOptions, "");
|
||||
}
|
||||
|
||||
public CheckVisitor(CheckOptions checkOptions, String script) {
|
||||
this.operatorCheckStrategy = checkOptions.getCheckStrategy();
|
||||
this.disableFunctionCalls = checkOptions.isDisableFunctionCalls();
|
||||
this.script = script;
|
||||
}
|
||||
|
||||
private void checkOperator(String operatorString, Token token)
|
||||
throws QLSyntaxException {
|
||||
if (null != operatorCheckStrategy && !operatorCheckStrategy.isAllowed(operatorString)) {
|
||||
String reason = String.format(QLErrorCodes.OPERATOR_NOT_ALLOWED.getErrorMsg(),
|
||||
operatorString,
|
||||
operatorCheckStrategy.getOperators());
|
||||
throw QLException.reportScannerErr(script,
|
||||
token.getStartIndex(),
|
||||
token.getLine(),
|
||||
token.getCharPositionInLine(),
|
||||
operatorString,
|
||||
QLErrorCodes.OPERATOR_NOT_ALLOWED.name(),
|
||||
reason);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkFunctionCall(Token token)
|
||||
throws QLSyntaxException {
|
||||
if (disableFunctionCalls) {
|
||||
String reason = "Function calls are not allowed in this context";
|
||||
throw QLException.reportScannerErr(script,
|
||||
token.getStartIndex(),
|
||||
token.getLine(),
|
||||
token.getCharPositionInLine(),
|
||||
token.getText(),
|
||||
"FUNCTION_CALL_NOT_ALLOWED",
|
||||
reason);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitLeftAsso(QLParser.LeftAssoContext ctx) {
|
||||
// Get operator
|
||||
QLParser.BinaryopContext binaryopContext = ctx.binaryop();
|
||||
if (binaryopContext != null) {
|
||||
String operator = binaryopContext.getText();
|
||||
checkOperator(operator, binaryopContext.getStart()); // Validate here, may throw exception
|
||||
}
|
||||
|
||||
// Continue traversing child nodes
|
||||
return super.visitLeftAsso(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitPrefixExpress(QLParser.PrefixExpressContext ctx) {
|
||||
// Get prefix operator
|
||||
if (ctx.opId() != null) {
|
||||
String operator = ctx.opId().getText();
|
||||
checkOperator(operator, ctx.opId().getStart()); // Validate here, may throw exception
|
||||
}
|
||||
|
||||
return super.visitPrefixExpress(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitSuffixExpress(QLParser.SuffixExpressContext ctx) {
|
||||
// Get suffix operator
|
||||
if (ctx.opId() != null) {
|
||||
String operator = ctx.opId().getText();
|
||||
checkOperator(operator, ctx.opId().getStart());
|
||||
}
|
||||
|
||||
return super.visitSuffixExpress(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitExpression(QLParser.ExpressionContext ctx) {
|
||||
// Check assignment operator
|
||||
if (ctx.assignOperator() != null) {
|
||||
checkOperator(ctx.assignOperator().getText(), ctx.assignOperator().getStart());
|
||||
}
|
||||
|
||||
return super.visitExpression(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitCallExpr(QLParser.CallExprContext ctx) {
|
||||
// Check if function calls are disabled
|
||||
checkFunctionCall(ctx.getStart());
|
||||
return super.visitCallExpr(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitMethodInvoke(QLParser.MethodInvokeContext ctx) {
|
||||
// Check if function calls are disabled
|
||||
checkFunctionCall(ctx.getStart());
|
||||
return super.visitMethodInvoke(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitOptionalMethodInvoke(QLParser.OptionalMethodInvokeContext ctx) {
|
||||
// Check if function calls are disabled
|
||||
checkFunctionCall(ctx.getStart());
|
||||
return super.visitOptionalMethodInvoke(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitSpreadMethodInvoke(QLParser.SpreadMethodInvokeContext ctx) {
|
||||
// Check if function calls are disabled
|
||||
checkFunctionCall(ctx.getStart());
|
||||
return super.visitSpreadMethodInvoke(ctx);
|
||||
}
|
||||
}
|
||||
12
src/main/java/com/alibaba/qlexpress4/aparser/ExistStack.java
Normal file
12
src/main/java/com/alibaba/qlexpress4/aparser/ExistStack.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
public interface ExistStack {
|
||||
|
||||
ExistStack push();
|
||||
|
||||
ExistStack pop();
|
||||
|
||||
boolean exist(String varName);
|
||||
|
||||
void add(String varName);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class GeneratorScope {
|
||||
|
||||
private final GeneratorScope parent;
|
||||
|
||||
private final String name;
|
||||
|
||||
private final Map<String, MacroDefine> macroDefineMap;
|
||||
|
||||
public GeneratorScope(GeneratorScope parent, String name, Map<String, MacroDefine> macroDefineMap) {
|
||||
this.parent = parent;
|
||||
this.name = name;
|
||||
this.macroDefineMap = macroDefineMap;
|
||||
}
|
||||
|
||||
public GeneratorScope(String name, GeneratorScope parent) {
|
||||
this.parent = parent;
|
||||
this.name = name;
|
||||
this.macroDefineMap = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name macro name
|
||||
* @param macroDefine macro definition
|
||||
* @return true if define macro successfully. fail if macro name already exists
|
||||
*/
|
||||
public boolean defineMacroIfAbsent(String name, MacroDefine macroDefine) {
|
||||
return macroDefineMap.putIfAbsent(name, macroDefine) == null;
|
||||
}
|
||||
|
||||
public void defineMacro(String name, MacroDefine macroDefine) {
|
||||
macroDefineMap.put(name, macroDefine);
|
||||
}
|
||||
|
||||
public MacroDefine getMacroInstructions(String macroName) {
|
||||
MacroDefine qlInstructions = macroDefineMap.get(macroName);
|
||||
return qlInstructions != null ? qlInstructions : parent != null ? parent.getMacroInstructions(macroName) : null;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
231
src/main/java/com/alibaba/qlexpress4/aparser/ImportManager.java
Normal file
231
src/main/java/com/alibaba/qlexpress4/aparser/ImportManager.java
Normal file
@@ -0,0 +1,231 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
import com.alibaba.qlexpress4.ClassSupplier;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class ImportManager {
|
||||
|
||||
private final ClassSupplier classSupplier;
|
||||
|
||||
private final List<QLImport> importedPacks;
|
||||
|
||||
private final Map<String, Class<?>> importedClses;
|
||||
|
||||
public ImportManager(ClassSupplier classSupplier, List<QLImport> imports) {
|
||||
this.classSupplier = classSupplier;
|
||||
this.importedPacks = new ArrayList<>();
|
||||
this.importedClses = new HashMap<>();
|
||||
imports.forEach(this::addImport);
|
||||
}
|
||||
|
||||
public ImportManager(ClassSupplier classSupplier, List<QLImport> importedPacks,
|
||||
Map<String, Class<?>> importedClses) {
|
||||
this.classSupplier = classSupplier;
|
||||
this.importedPacks = importedPacks;
|
||||
this.importedClses = importedClses;
|
||||
}
|
||||
|
||||
public boolean addImport(QLImport anImport) {
|
||||
switch (anImport.getScope()) {
|
||||
case PACK:
|
||||
case InnerCls:
|
||||
importedPacks.add(anImport);
|
||||
return true;
|
||||
case CLS:
|
||||
Class<?> importCls = classSupplier.loadCls(anImport.getTarget());
|
||||
if (importCls == null) {
|
||||
return false;
|
||||
}
|
||||
String[] split = anImport.getTarget().split("\\.");
|
||||
importedClses.put(split[split.length - 1], importCls);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Class<?> loadQualified(String qualifiedCls) {
|
||||
return classSupplier.loadCls(qualifiedCls);
|
||||
}
|
||||
|
||||
public LoadPartQualifiedResult loadPartQualified(List<String> fieldIds) {
|
||||
Class<?> qualifiedCls = null;
|
||||
List<String> qualifiedPath = null;
|
||||
String innerClsId = null;
|
||||
final byte initState = 0;
|
||||
final byte continueState = 1;
|
||||
final byte loadClsState = 2;
|
||||
final byte loadInnerClsState = 3;
|
||||
final byte preLoadInnerClsState = 4;
|
||||
byte state = initState;
|
||||
nextField: for (int i = 0; i < fieldIds.size(); i++) {
|
||||
String fieldId = fieldIds.get(i);
|
||||
switch (state) {
|
||||
case initState:
|
||||
// load from imported class
|
||||
Class<?> aCls = importedClses.get(fieldId);
|
||||
if (aCls != null) {
|
||||
qualifiedCls = aCls;
|
||||
state = preLoadInnerClsState;
|
||||
continue;
|
||||
}
|
||||
// load from imported packs
|
||||
if (!Character.isLowerCase(fieldId.charAt(0))) {
|
||||
for (QLImport importedPack : importedPacks) {
|
||||
switch (importedPack.getScope()) {
|
||||
case PACK:
|
||||
Class<?> packCls = classSupplier.loadCls(importedPack.getTarget() + "." + fieldId);
|
||||
if (packCls != null) {
|
||||
qualifiedCls = packCls;
|
||||
state = preLoadInnerClsState;
|
||||
continue nextField;
|
||||
}
|
||||
break;
|
||||
case InnerCls:
|
||||
Class<?> innerCls = classSupplier.loadCls(importedPack.getTarget() + "$" + fieldId);
|
||||
if (innerCls != null) {
|
||||
qualifiedCls = innerCls;
|
||||
state = preLoadInnerClsState;
|
||||
continue nextField;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new LoadPartQualifiedResult(null, 0);
|
||||
}
|
||||
state = continueState;
|
||||
qualifiedPath = new ArrayList<>();
|
||||
qualifiedPath.add(fieldId);
|
||||
break;
|
||||
case preLoadInnerClsState:
|
||||
if (!Character.isLowerCase(fieldId.charAt(0))) {
|
||||
state = loadInnerClsState;
|
||||
innerClsId = fieldId;
|
||||
}
|
||||
else {
|
||||
return new LoadPartQualifiedResult(qualifiedCls, i);
|
||||
}
|
||||
break;
|
||||
case continueState:
|
||||
qualifiedPath.add(fieldId);
|
||||
if (!Character.isLowerCase(fieldId.charAt(0))) {
|
||||
state = loadClsState;
|
||||
}
|
||||
break;
|
||||
case loadClsState:
|
||||
qualifiedCls = classSupplier.loadCls(String.join(".", qualifiedPath));
|
||||
if (qualifiedCls == null) {
|
||||
return new LoadPartQualifiedResult(null, 0);
|
||||
}
|
||||
if (!Character.isLowerCase(fieldId.charAt(0))) {
|
||||
qualifiedPath = null;
|
||||
innerClsId = fieldId;
|
||||
state = loadInnerClsState;
|
||||
}
|
||||
else {
|
||||
return new LoadPartQualifiedResult(qualifiedCls, i);
|
||||
}
|
||||
break;
|
||||
case loadInnerClsState:
|
||||
Class<?> innerCls = classSupplier.loadCls(qualifiedCls.getName() + "$" + innerClsId);
|
||||
if (innerCls == null) {
|
||||
return new LoadPartQualifiedResult(qualifiedCls, i - 1);
|
||||
}
|
||||
if (!Character.isLowerCase(fieldId.charAt(0))) {
|
||||
qualifiedCls = innerCls;
|
||||
innerClsId = fieldId;
|
||||
}
|
||||
else {
|
||||
return new LoadPartQualifiedResult(innerCls, i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case continueState:
|
||||
return new LoadPartQualifiedResult(null, 0);
|
||||
case loadClsState:
|
||||
qualifiedCls = classSupplier.loadCls(String.join(".", qualifiedPath));
|
||||
return qualifiedCls == null ? new LoadPartQualifiedResult(null, fieldIds.size())
|
||||
: new LoadPartQualifiedResult(qualifiedCls, fieldIds.size());
|
||||
case preLoadInnerClsState:
|
||||
return new LoadPartQualifiedResult(qualifiedCls, fieldIds.size());
|
||||
case loadInnerClsState:
|
||||
Class<?> innerCls = classSupplier.loadCls(qualifiedCls.getName() + "$" + innerClsId);
|
||||
return innerCls == null ? new LoadPartQualifiedResult(qualifiedCls, fieldIds.size() - 1)
|
||||
: new LoadPartQualifiedResult(innerCls, fieldIds.size());
|
||||
default:
|
||||
return new LoadPartQualifiedResult(null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static class LoadPartQualifiedResult {
|
||||
private final Class<?> cls;
|
||||
|
||||
/**
|
||||
* first no class path field index
|
||||
*/
|
||||
private final int restIndex;
|
||||
|
||||
public LoadPartQualifiedResult(Class<?> cls, int restIndex) {
|
||||
this.cls = cls;
|
||||
this.restIndex = restIndex;
|
||||
}
|
||||
|
||||
public Class<?> getCls() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
public int getRestIndex() {
|
||||
return restIndex;
|
||||
}
|
||||
}
|
||||
|
||||
enum ImportScope {
|
||||
// import java.lang.*;
|
||||
PACK,
|
||||
// import a.b.Cls.*
|
||||
InnerCls,
|
||||
// import java.lang.String;
|
||||
CLS
|
||||
}
|
||||
|
||||
public static QLImport importInnerCls(String clsPath) {
|
||||
return new QLImport(ImportScope.InnerCls, clsPath);
|
||||
}
|
||||
|
||||
public static QLImport importPack(String packPath) {
|
||||
return new QLImport(ImportScope.PACK, packPath);
|
||||
}
|
||||
|
||||
public static QLImport importCls(String clsPath) {
|
||||
return new QLImport(ImportScope.CLS, clsPath);
|
||||
}
|
||||
|
||||
public static class QLImport {
|
||||
private final ImportScope scope;
|
||||
|
||||
private final String target;
|
||||
|
||||
public QLImport(ImportScope scope, String target) {
|
||||
this.scope = scope;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public ImportScope getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public String getTarget() {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
/**
|
||||
* How to manage string interpolation, for instance, "a ${t-c} b"
|
||||
*/
|
||||
public enum InterpolationMode {
|
||||
/**
|
||||
* Implement interpolation using a QLExpress script.
|
||||
*/
|
||||
SCRIPT,
|
||||
/**
|
||||
* Implement interpolation using a variable name in context.
|
||||
*/
|
||||
VARIABLE,
|
||||
/**
|
||||
* Disable interpolation, `${xxx}` will be rendered verbatim.
|
||||
*/
|
||||
DISABLE
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
import com.alibaba.qlexpress4.runtime.instruction.QLInstruction;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class MacroDefine {
|
||||
|
||||
private final List<QLInstruction> instructions;
|
||||
|
||||
private final boolean lastStmtExpress;
|
||||
|
||||
public MacroDefine(List<QLInstruction> instructions, boolean lastStmtExpress) {
|
||||
this.instructions = instructions;
|
||||
this.lastStmtExpress = lastStmtExpress;
|
||||
}
|
||||
|
||||
public List<QLInstruction> getMacroInstructions() {
|
||||
return instructions;
|
||||
}
|
||||
|
||||
public boolean isLastStmtExpress() {
|
||||
return lastStmtExpress;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
import com.alibaba.qlexpress4.runtime.operator.BinaryOperator;
|
||||
import com.alibaba.qlexpress4.runtime.operator.unary.UnaryOperator;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public interface OperatorFactory {
|
||||
|
||||
BinaryOperator getBinaryOperator(String operatorLexeme);
|
||||
|
||||
UnaryOperator getPrefixUnaryOperator(String operatorLexeme);
|
||||
|
||||
UnaryOperator getSuffixUnaryOperator(String operatorLexeme);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
import com.alibaba.qlexpress4.exception.PureErrReporter;
|
||||
import com.alibaba.qlexpress4.runtime.instruction.PopInstruction;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class OutFunctionVisitor extends ScopeStackVisitor {
|
||||
|
||||
private final Set<String> outFunctions = new HashSet<>();
|
||||
|
||||
public OutFunctionVisitor() {
|
||||
super(new ExistFunctionStack(null));
|
||||
}
|
||||
|
||||
private static class ExistFunctionStack implements ExistStack {
|
||||
private final ExistFunctionStack parent;
|
||||
|
||||
private final Set<String> existVars = new HashSet<>();
|
||||
|
||||
private ExistFunctionStack(ExistFunctionStack parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public void add(String varName) {
|
||||
existVars.add(varName);
|
||||
}
|
||||
|
||||
public boolean exist(String varName) {
|
||||
if (existVars.contains(varName)) {
|
||||
return true;
|
||||
}
|
||||
return parent != null && parent.exist(varName);
|
||||
}
|
||||
|
||||
public ExistFunctionStack push() {
|
||||
return new ExistFunctionStack(this);
|
||||
}
|
||||
|
||||
public ExistFunctionStack pop() {
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitBlockStatements(QLParser.BlockStatementsContext ctx) {
|
||||
List<QLParser.BlockStatementContext> nonEmptyChildren = ctx.blockStatement()
|
||||
.stream()
|
||||
.filter(bs -> !(bs instanceof QLParser.EmptyStatementContext))
|
||||
.collect(Collectors.toList());
|
||||
// process all function definitions to support forward references
|
||||
for (QLParser.BlockStatementContext child : nonEmptyChildren) {
|
||||
if (child instanceof QLParser.FunctionStatementContext) {
|
||||
child.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
for (QLParser.BlockStatementContext child : nonEmptyChildren) {
|
||||
if (!(child instanceof QLParser.FunctionStatementContext)) {
|
||||
child.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitPrimary(QLParser.PrimaryContext ctx) {
|
||||
QLParser.PrimaryNoFixPathableContext primaryNoFixPathableContext = ctx.primaryNoFixPathable();
|
||||
if (primaryNoFixPathableContext != null) {
|
||||
List<QLParser.PathPartContext> pathPartContexts = ctx.pathPart();
|
||||
if (primaryNoFixPathableContext instanceof QLParser.VarIdExprContext && !pathPartContexts.isEmpty()
|
||||
&& pathPartContexts.get(0) instanceof QLParser.CallExprContext) {
|
||||
// function call
|
||||
String functionName = primaryNoFixPathableContext.getText();
|
||||
if (!getStack().exist(functionName)) {
|
||||
outFunctions.add(functionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.visitPrimary(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitFunctionStatement(QLParser.FunctionStatementContext ctx) {
|
||||
String functionName = ctx.varId().getText();
|
||||
getStack().add(functionName);
|
||||
|
||||
QLParser.FormalOrInferredParameterListContext paramList = ctx.formalOrInferredParameterList();
|
||||
if (paramList != null) {
|
||||
paramList.accept(this);
|
||||
}
|
||||
|
||||
QLParser.BlockStatementsContext functionBlockStatements = ctx.blockStatements();
|
||||
if (functionBlockStatements != null) {
|
||||
push();
|
||||
// recur scene
|
||||
getStack().add(functionName);
|
||||
functionBlockStatements.accept(this);
|
||||
pop();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Set<String> getOutFunctions() {
|
||||
return outFunctions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
import com.alibaba.qlexpress4.utils.QLStringUtils;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class OutVarAttrsVisitor extends ScopeStackVisitor {
|
||||
|
||||
private final Set<List<String>> outVarAttrs = new HashSet<>();
|
||||
|
||||
private final ImportManager importManager;
|
||||
|
||||
public OutVarAttrsVisitor(ImportManager importManager) {
|
||||
super(new ExistVarStack(null));
|
||||
this.importManager = importManager;
|
||||
}
|
||||
|
||||
private static class ExistVarStack implements ExistStack {
|
||||
private final ExistVarStack parent;
|
||||
|
||||
private final Set<String> existVars = new HashSet<>();
|
||||
|
||||
private ExistVarStack(ExistVarStack parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public void add(String varName) {
|
||||
existVars.add(varName);
|
||||
}
|
||||
|
||||
public boolean exist(String varName) {
|
||||
if (existVars.contains(varName)) {
|
||||
return true;
|
||||
}
|
||||
return parent != null && parent.exist(varName);
|
||||
}
|
||||
|
||||
public ExistVarStack push() {
|
||||
return new ExistVarStack(this);
|
||||
}
|
||||
|
||||
public ExistVarStack pop() {
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
|
||||
// handle import
|
||||
|
||||
@Override
|
||||
public Void visitImportCls(QLParser.ImportClsContext ctx) {
|
||||
String importClsPath = ctx.varId()
|
||||
.stream()
|
||||
.map(QLParser.VarIdContext::getStart)
|
||||
.map(Token::getText)
|
||||
.collect(Collectors.joining("."));
|
||||
importManager.addImport(ImportManager.importCls(importClsPath));
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitImportPack(QLParser.ImportPackContext ctx) {
|
||||
List<QLParser.VarIdContext> importPackPathTokens = ctx.varId();
|
||||
boolean isInnerCls =
|
||||
!Character.isLowerCase(importPackPathTokens.get(importPackPathTokens.size() - 1).getText().charAt(0));
|
||||
String importPath = importPackPathTokens.stream()
|
||||
.map(QLParser.VarIdContext::getStart)
|
||||
.map(Token::getText)
|
||||
.collect(Collectors.joining("."));
|
||||
importManager
|
||||
.addImport(isInnerCls ? ImportManager.importInnerCls(importPath) : ImportManager.importPack(importPath));
|
||||
return null;
|
||||
}
|
||||
|
||||
// collect exist variable name
|
||||
|
||||
/**
|
||||
* @param ctx int a = 10;
|
||||
* @return a
|
||||
*/
|
||||
@Override
|
||||
public Void visitVariableDeclaratorId(QLParser.VariableDeclaratorIdContext ctx) {
|
||||
QLParser.VarIdContext varIdContext = ctx.varId();
|
||||
getStack().add(varIdContext.getText());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitLeftHandSide(QLParser.LeftHandSideContext ctx) {
|
||||
List<QLParser.PathPartContext> pathPartContexts = ctx.pathPart();
|
||||
String leftVarName = ctx.varId().getText();
|
||||
if (pathPartContexts.isEmpty()) {
|
||||
getStack().add(leftVarName);
|
||||
}
|
||||
else if (!getStack().exist(leftVarName)) {
|
||||
addAttrs(leftVarName, pathPartContexts);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// exclude function call
|
||||
|
||||
@Override
|
||||
public Void visitPrimary(QLParser.PrimaryContext ctx) {
|
||||
QLParser.PrimaryNoFixPathableContext primaryNoFixPathableContext = ctx.primaryNoFixPathable();
|
||||
if (primaryNoFixPathableContext != null) {
|
||||
List<QLParser.PathPartContext> pathPartContexts = ctx.pathPart();
|
||||
if (primaryNoFixPathableContext instanceof QLParser.VarIdExprContext && !pathPartContexts.isEmpty()
|
||||
&& pathPartContexts.get(0) instanceof QLParser.CallExprContext) {
|
||||
// function call
|
||||
for (QLParser.PathPartContext pathPartContext : pathPartContexts) {
|
||||
pathPartContext.accept(this);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (primaryNoFixPathableContext instanceof QLParser.VarIdExprContext) {
|
||||
int restIndex = parseOutVarAttrInPath(((QLParser.VarIdExprContext)primaryNoFixPathableContext).varId(),
|
||||
pathPartContexts);
|
||||
for (int i = restIndex; i < pathPartContexts.size(); i++) {
|
||||
pathPartContexts.get(i).accept(this);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return super.visitPrimary(ctx);
|
||||
}
|
||||
|
||||
private int parseOutVarAttrInPath(QLParser.VarIdContext idContext,
|
||||
List<QLParser.PathPartContext> pathPartContexts) {
|
||||
List<String> headPartIds = new ArrayList<>();
|
||||
String primaryId = idContext.getText();
|
||||
headPartIds.add(primaryId);
|
||||
for (QLParser.PathPartContext pathPartContext : pathPartContexts) {
|
||||
if (pathPartContext instanceof QLParser.FieldAccessContext) {
|
||||
headPartIds.add(parseFieldId(((QLParser.FieldAccessContext)pathPartContext).fieldId()));
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ImportManager.LoadPartQualifiedResult loadPartQualifiedResult = importManager.loadPartQualified(headPartIds);
|
||||
if (loadPartQualifiedResult.getCls() != null) {
|
||||
return loadPartQualifiedResult.getRestIndex() - 1;
|
||||
}
|
||||
else {
|
||||
return getStack().exist(primaryId) ? 0 : addAttrs(primaryId, pathPartContexts);
|
||||
}
|
||||
}
|
||||
|
||||
private String parseFieldId(QLParser.FieldIdContext ctx) {
|
||||
TerminalNode quoteStringLiteral = ctx.QuoteStringLiteral();
|
||||
if (quoteStringLiteral != null) {
|
||||
return QLStringUtils.parseStringEscape(quoteStringLiteral.getText());
|
||||
}
|
||||
return ctx.getStart().getText();
|
||||
}
|
||||
|
||||
private String getFieldId(QLParser.PathPartContext pathPartContext) {
|
||||
if (pathPartContext instanceof QLParser.FieldAccessContext) {
|
||||
return parseFieldId(((QLParser.FieldAccessContext)pathPartContext).fieldId());
|
||||
}
|
||||
else if (pathPartContext instanceof QLParser.OptionalFieldAccessContext) {
|
||||
return parseFieldId(((QLParser.OptionalFieldAccessContext)pathPartContext).fieldId());
|
||||
}
|
||||
else if (pathPartContext instanceof QLParser.SpreadFieldAccessContext) {
|
||||
return parseFieldId(((QLParser.SpreadFieldAccessContext)pathPartContext).fieldId());
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private int addAttrs(String primaryId, List<QLParser.PathPartContext> pathPartContexts) {
|
||||
List<String> attrs = new ArrayList<>();
|
||||
attrs.add(primaryId);
|
||||
|
||||
int i = 0;
|
||||
for (; i < pathPartContexts.size(); i++) {
|
||||
String fieldId = getFieldId(pathPartContexts.get(i));
|
||||
if (fieldId == null) {
|
||||
break;
|
||||
}
|
||||
attrs.add(fieldId);
|
||||
}
|
||||
|
||||
outVarAttrs.add(attrs);
|
||||
return i;
|
||||
}
|
||||
|
||||
public Set<List<String>> getOutVarAttrs() {
|
||||
return outVarAttrs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
import com.alibaba.qlexpress4.utils.QLStringUtils;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class OutVarNamesVisitor extends ScopeStackVisitor {
|
||||
|
||||
private final Set<String> outVars = new HashSet<>();
|
||||
|
||||
private final ImportManager importManager;
|
||||
|
||||
public OutVarNamesVisitor(ImportManager importManager) {
|
||||
super(new ExistVarStack(null));
|
||||
this.importManager = importManager;
|
||||
}
|
||||
|
||||
private static class ExistVarStack implements ExistStack {
|
||||
private final ExistVarStack parent;
|
||||
|
||||
private final Set<String> existVars = new HashSet<>();
|
||||
|
||||
private ExistVarStack(ExistVarStack parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public void add(String varName) {
|
||||
existVars.add(varName);
|
||||
}
|
||||
|
||||
public boolean exist(String varName) {
|
||||
if (existVars.contains(varName)) {
|
||||
return true;
|
||||
}
|
||||
return parent != null && parent.exist(varName);
|
||||
}
|
||||
|
||||
public ExistVarStack push() {
|
||||
return new ExistVarStack(this);
|
||||
}
|
||||
|
||||
public ExistVarStack pop() {
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
|
||||
// handle import
|
||||
|
||||
@Override
|
||||
public Void visitImportCls(QLParser.ImportClsContext ctx) {
|
||||
String importClsPath = ctx.varId()
|
||||
.stream()
|
||||
.map(QLParser.VarIdContext::getStart)
|
||||
.map(Token::getText)
|
||||
.collect(Collectors.joining("."));
|
||||
importManager.addImport(ImportManager.importCls(importClsPath));
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitImportPack(QLParser.ImportPackContext ctx) {
|
||||
List<QLParser.VarIdContext> importPackPathTokens = ctx.varId();
|
||||
boolean isInnerCls =
|
||||
!Character.isLowerCase(importPackPathTokens.get(importPackPathTokens.size() - 1).getText().charAt(0));
|
||||
String importPath = importPackPathTokens.stream()
|
||||
.map(QLParser.VarIdContext::getStart)
|
||||
.map(Token::getText)
|
||||
.collect(Collectors.joining("."));
|
||||
importManager
|
||||
.addImport(isInnerCls ? ImportManager.importInnerCls(importPath) : ImportManager.importPack(importPath));
|
||||
return null;
|
||||
}
|
||||
|
||||
// collect exist variable name
|
||||
|
||||
/**
|
||||
* @param ctx int a = 10;
|
||||
* @return a
|
||||
*/
|
||||
@Override
|
||||
public Void visitVariableDeclaratorId(QLParser.VariableDeclaratorIdContext ctx) {
|
||||
QLParser.VarIdContext varIdContext = ctx.varId();
|
||||
getStack().add(varIdContext.getText());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitLeftHandSide(QLParser.LeftHandSideContext ctx) {
|
||||
List<QLParser.PathPartContext> pathPartContexts = ctx.pathPart();
|
||||
String leftVarName = ctx.varId().getText();
|
||||
if (pathPartContexts.isEmpty()) {
|
||||
getStack().add(leftVarName);
|
||||
}
|
||||
else if (!getStack().exist(leftVarName)) {
|
||||
outVars.add(leftVarName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// selector
|
||||
|
||||
@Override
|
||||
public Void visitContextSelectExpr(QLParser.ContextSelectExprContext ctx) {
|
||||
String variableName = ctx.SelectorVariable_VANME().getText().trim();
|
||||
if (!getStack().exist(variableName)) {
|
||||
outVars.add(variableName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// exclude function call
|
||||
|
||||
@Override
|
||||
public Void visitPrimary(QLParser.PrimaryContext ctx) {
|
||||
QLParser.PrimaryNoFixPathableContext primaryNoFixPathableContext = ctx.primaryNoFixPathable();
|
||||
if (primaryNoFixPathableContext != null) {
|
||||
List<QLParser.PathPartContext> pathPartContexts = ctx.pathPart();
|
||||
if (primaryNoFixPathableContext instanceof QLParser.VarIdExprContext && !pathPartContexts.isEmpty()
|
||||
&& pathPartContexts.get(0) instanceof QLParser.CallExprContext) {
|
||||
// function call
|
||||
for (QLParser.PathPartContext pathPartContext : pathPartContexts) {
|
||||
pathPartContext.accept(this);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (primaryNoFixPathableContext instanceof QLParser.VarIdExprContext) {
|
||||
int restIndex = parseVarIdInPath(((QLParser.VarIdExprContext)primaryNoFixPathableContext).varId(),
|
||||
pathPartContexts);
|
||||
for (int i = restIndex; i < pathPartContexts.size(); i++) {
|
||||
pathPartContexts.get(i).accept(this);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return super.visitPrimary(ctx);
|
||||
}
|
||||
|
||||
private int parseVarIdInPath(QLParser.VarIdContext idContext, List<QLParser.PathPartContext> pathPartContexts) {
|
||||
List<String> headPartIds = new ArrayList<>();
|
||||
String primaryId = idContext.getText();
|
||||
headPartIds.add(primaryId);
|
||||
for (QLParser.PathPartContext pathPartContext : pathPartContexts) {
|
||||
if (pathPartContext instanceof QLParser.FieldAccessContext) {
|
||||
headPartIds.add(parseFieldId(((QLParser.FieldAccessContext)pathPartContext).fieldId()));
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ImportManager.LoadPartQualifiedResult loadPartQualifiedResult = importManager.loadPartQualified(headPartIds);
|
||||
if (loadPartQualifiedResult.getCls() != null) {
|
||||
return loadPartQualifiedResult.getRestIndex() - 1;
|
||||
}
|
||||
else {
|
||||
if (!getStack().exist(primaryId)) {
|
||||
outVars.add(primaryId);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private String parseFieldId(QLParser.FieldIdContext ctx) {
|
||||
TerminalNode quoteStringLiteral = ctx.QuoteStringLiteral();
|
||||
if (quoteStringLiteral != null) {
|
||||
return QLStringUtils.parseStringEscape(quoteStringLiteral.getText());
|
||||
}
|
||||
return ctx.getStart().getText();
|
||||
}
|
||||
|
||||
// collect out variables name
|
||||
|
||||
@Override
|
||||
public Void visitVarIdExpr(QLParser.VarIdExprContext ctx) {
|
||||
String varName = ctx.varId().getText();
|
||||
if (!getStack().exist(varName)) {
|
||||
outVars.add(varName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Set<String> getOutVars() {
|
||||
return outVars;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public interface ParserOperatorManager {
|
||||
|
||||
enum OpType {
|
||||
PREFIX, SUFFIX, MIDDLE
|
||||
}
|
||||
|
||||
/**
|
||||
* determine whether lexeme is opType or not
|
||||
* @param lexeme lexeme
|
||||
* @param opType type of operator
|
||||
* @return true if lexeme is opType
|
||||
*/
|
||||
boolean isOpType(String lexeme, OpType opType);
|
||||
|
||||
/**
|
||||
* get binary operator precedence
|
||||
* @param lexeme lexeme
|
||||
* @return null if lexeme not a operator
|
||||
*/
|
||||
Integer precedence(String lexeme);
|
||||
|
||||
/**
|
||||
* Get the aliased token type of lexeme.
|
||||
*
|
||||
* @param lexeme the source text of the token
|
||||
* @return alias token type (or {@code null} if none)
|
||||
*/
|
||||
Integer getAlias(String lexeme);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
import com.alibaba.qlexpress4.runtime.QLambdaDefinition;
|
||||
import com.alibaba.qlexpress4.runtime.trace.TracePointTree;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class QCompileCache {
|
||||
|
||||
private final QLambdaDefinition qLambdaDefinition;
|
||||
|
||||
private final List<TracePointTree> expressionTracePoints;
|
||||
|
||||
public QCompileCache(QLambdaDefinition qLambdaDefinition, List<TracePointTree> expressionTracePoints) {
|
||||
this.qLambdaDefinition = qLambdaDefinition;
|
||||
this.expressionTracePoints = expressionTracePoints;
|
||||
}
|
||||
|
||||
public QLambdaDefinition getQLambdaDefinition() {
|
||||
return qLambdaDefinition;
|
||||
}
|
||||
|
||||
public List<TracePointTree> getExpressionTracePoints() {
|
||||
return expressionTracePoints;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
import com.alibaba.qlexpress4.exception.QLErrorCodes;
|
||||
import com.alibaba.qlexpress4.exception.QLException;
|
||||
import org.antlr.v4.runtime.BaseErrorListener;
|
||||
import org.antlr.v4.runtime.Parser;
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.RecognitionException;
|
||||
import org.antlr.v4.runtime.Recognizer;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
public class QLErrorListener extends BaseErrorListener {
|
||||
|
||||
private final String script;
|
||||
|
||||
public QLErrorListener(String script) {
|
||||
this.script = script;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine,
|
||||
String msg, RecognitionException e) {
|
||||
Token currentToken = (Token)offendingSymbol;
|
||||
String tokenText = currentToken.getType() == Token.EOF ? "<EOF>" : currentToken.getText();
|
||||
String preHandledScript = currentToken.getType() == Token.EOF ? script + "<EOF>" : script;
|
||||
String preHandledMsg = errMsg(((Parser)recognizer).getContext(), currentToken, msg);
|
||||
|
||||
throw QLException.reportScannerErr(preHandledScript,
|
||||
currentToken.getStartIndex(),
|
||||
currentToken.getLine(),
|
||||
currentToken.getCharPositionInLine(),
|
||||
tokenText,
|
||||
QLErrorCodes.SYNTAX_ERROR.name(),
|
||||
preHandledMsg);
|
||||
}
|
||||
|
||||
private String errMsg(ParserRuleContext ruleContext, Token currentToken, String msg) {
|
||||
if ("'".equals(currentToken.getText()) || "\"".equals(currentToken.getText())
|
||||
|| ruleContext.getRuleIndex() == QLParser.RULE_doubleQuoteStringLiteral) {
|
||||
return "unterminated string literal";
|
||||
}
|
||||
if ("import".equals(currentToken.getText())) {
|
||||
return "Import statement is not at the beginning of the file.";
|
||||
}
|
||||
if (ruleContext.getRuleIndex() == QLParser.RULE_importDeclaration && "static".equals(currentToken.getText())) {
|
||||
return "'import static' not supported temporarily";
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
import org.antlr.v4.runtime.DefaultErrorStrategy;
|
||||
import org.antlr.v4.runtime.InputMismatchException;
|
||||
import org.antlr.v4.runtime.Parser;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.Vocabulary;
|
||||
import org.antlr.v4.runtime.misc.Interval;
|
||||
import org.antlr.v4.runtime.misc.IntervalSet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class QLErrorStrategy extends DefaultErrorStrategy {
|
||||
|
||||
@Override
|
||||
protected void reportInputMismatch(Parser recognizer, InputMismatchException e) {
|
||||
String msg = "mismatched input " + getTokenErrorDisplay(e.getOffendingToken()) + " expecting "
|
||||
+ intervalSetString(e.getExpectedTokens(), recognizer.getVocabulary());
|
||||
recognizer.notifyErrorListeners(e.getOffendingToken(), msg, e);
|
||||
}
|
||||
|
||||
private static String intervalSetString(IntervalSet expectedTokens, Vocabulary vocabulary) {
|
||||
if (expectedTokens.getIntervals() == null || expectedTokens.getIntervals().isEmpty()) {
|
||||
return "{}";
|
||||
}
|
||||
|
||||
List<String> eleNames = new ArrayList<>();
|
||||
for (Interval I : expectedTokens.getIntervals()) {
|
||||
int a = I.a;
|
||||
int b = I.b;
|
||||
for (int i = a; i <= b; i++) {
|
||||
if (i != QLexer.NEWLINE) {
|
||||
eleNames.add(elementName(vocabulary, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (eleNames.isEmpty()) {
|
||||
return "{}";
|
||||
}
|
||||
if (eleNames.size() == 1) {
|
||||
return eleNames.get(0);
|
||||
}
|
||||
return String.join(",", eleNames);
|
||||
}
|
||||
|
||||
private static String elementName(Vocabulary vocabulary, int a) {
|
||||
if (a == Token.EOF) {
|
||||
return "<EOF>";
|
||||
}
|
||||
else if (a == Token.EPSILON) {
|
||||
return "<EPSILON>";
|
||||
}
|
||||
else {
|
||||
return vocabulary.getDisplayName(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
103
src/main/java/com/alibaba/qlexpress4/aparser/QLExtendLexer.java
Normal file
103
src/main/java/com/alibaba/qlexpress4/aparser/QLExtendLexer.java
Normal file
@@ -0,0 +1,103 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
import com.alibaba.qlexpress4.exception.QLErrorCodes;
|
||||
import com.alibaba.qlexpress4.exception.QLException;
|
||||
import org.antlr.v4.runtime.CharStream;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
public class QLExtendLexer extends QLexer {
|
||||
|
||||
private final String script;
|
||||
|
||||
private final InterpolationMode interpolationMode;
|
||||
|
||||
private final String selectorStart;
|
||||
|
||||
private final String selectorEnd;
|
||||
|
||||
private final boolean strictNewLines;
|
||||
|
||||
public QLExtendLexer(CharStream input, String script, InterpolationMode interpolationMode, String selectorStart,
|
||||
String selectorEnd, boolean strictNewLines) {
|
||||
super(input);
|
||||
this.script = script;
|
||||
this.interpolationMode = interpolationMode;
|
||||
this.selectorStart = selectorStart;
|
||||
this.selectorEnd = selectorEnd;
|
||||
this.strictNewLines = strictNewLines;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Token nextToken() {
|
||||
Token token = super.nextToken();
|
||||
// In non-strict mode, skip NEWLINE tokens
|
||||
if (!strictNewLines && token.getType() == QLexer.NEWLINE) {
|
||||
// Skip NEWLINEs by recursively calling nextToken()
|
||||
return nextToken();
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InterpolationMode getInterpolationMode() {
|
||||
return interpolationMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSelectorStart() {
|
||||
return selectorStart;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void consumeSelectorVariable() {
|
||||
StringBuilder t = new StringBuilder();
|
||||
int selectorEndLength = selectorEnd.length();
|
||||
char lastCharOfSelector = selectorEnd.charAt(selectorEndLength - 1);
|
||||
|
||||
t.ensureCapacity(selectorEndLength * 2);
|
||||
|
||||
while (true) {
|
||||
int curChInt = _input.LA(1);
|
||||
if (curChInt == Token.EOF || curChInt == '\n') {
|
||||
// mismatch
|
||||
throwScannerException(t.toString(), "unterminated selector");
|
||||
}
|
||||
char curCh = (char)curChInt;
|
||||
t.append(curCh);
|
||||
_input.consume();
|
||||
|
||||
if (curCh == lastCharOfSelector && t.length() >= selectorEndLength) {
|
||||
if (checkEndsWith(t, selectorEnd)) {
|
||||
// match
|
||||
String text = t.toString();
|
||||
setText(text.substring(0, text.length() - selectorEndLength));
|
||||
popMode();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void throwScannerException(String lexeme, String reason) {
|
||||
throw QLException.reportScannerErr(script,
|
||||
this._tokenStartCharIndex,
|
||||
this._tokenStartLine,
|
||||
this._tokenStartCharPositionInLine,
|
||||
lexeme,
|
||||
QLErrorCodes.SYNTAX_ERROR.name(),
|
||||
reason);
|
||||
}
|
||||
|
||||
private boolean checkEndsWith(StringBuilder sb, String suffix) {
|
||||
int suffixLength = suffix.length();
|
||||
int sbLength = sb.length();
|
||||
|
||||
for (int i = 0; i < suffixLength; i++) {
|
||||
if (sb.charAt(sbLength - suffixLength + i) != suffix.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
import com.alibaba.qlexpress4.aparser.ParserOperatorManager.OpType;
|
||||
|
||||
public class QLExtendParser extends QLParser {
|
||||
|
||||
private final ParserOperatorManager opM;
|
||||
|
||||
private final InterpolationMode interpolationMode;
|
||||
|
||||
private final boolean strictNewLines;
|
||||
|
||||
public QLExtendParser(AliasTokenStream input, ParserOperatorManager opM, InterpolationMode interpolationMode,
|
||||
boolean strictNewLines) {
|
||||
super(input);
|
||||
this.opM = opM;
|
||||
this.interpolationMode = interpolationMode;
|
||||
this.strictNewLines = strictNewLines;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isOpType(String lexeme, OpType opType) {
|
||||
return opM.isOpType(lexeme, opType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer precedence(String lexeme) {
|
||||
return opM.precedence(lexeme);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InterpolationMode getInterpolationMode() {
|
||||
return interpolationMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isStrictNewLines() {
|
||||
return strictNewLines;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,99 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
public abstract class ScopeStackVisitor extends QLParserBaseVisitor<Void> {
|
||||
|
||||
private ExistStack existStack;
|
||||
|
||||
protected ScopeStackVisitor(ExistStack existStack) {
|
||||
this.existStack = existStack;
|
||||
}
|
||||
|
||||
// scope
|
||||
@Override
|
||||
public Void visitBlockExpr(QLParser.BlockExprContext ctx) {
|
||||
push();
|
||||
super.visitBlockExpr(ctx);
|
||||
pop();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitQlIf(QLParser.QlIfContext qlIfContext) {
|
||||
qlIfContext.condition.accept(this);
|
||||
|
||||
push();
|
||||
qlIfContext.thenBody().accept(this);
|
||||
pop();
|
||||
|
||||
QLParser.ElseBodyContext elseBodyContext = qlIfContext.elseBody();
|
||||
if (elseBodyContext != null) {
|
||||
push();
|
||||
elseBodyContext.accept(this);
|
||||
pop();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitTryCatchExpr(QLParser.TryCatchExprContext ctx) {
|
||||
QLParser.BlockStatementsContext blockStatementsContext = ctx.blockStatements();
|
||||
if (blockStatementsContext != null) {
|
||||
push();
|
||||
blockStatementsContext.accept(this);
|
||||
pop();
|
||||
}
|
||||
|
||||
QLParser.TryCatchesContext tryCatchesContext = ctx.tryCatches();
|
||||
if (tryCatchesContext != null) {
|
||||
tryCatchesContext.accept(this);
|
||||
}
|
||||
|
||||
QLParser.TryFinallyContext tryFinallyContext = ctx.tryFinally();
|
||||
if (tryFinallyContext != null) {
|
||||
push();
|
||||
tryFinallyContext.accept(this);
|
||||
pop();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitTryCatch(QLParser.TryCatchContext ctx) {
|
||||
push();
|
||||
super.visitTryCatch(ctx);
|
||||
pop();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitFunctionStatement(QLParser.FunctionStatementContext ctx) {
|
||||
ctx.varId().accept(this);
|
||||
QLParser.FormalOrInferredParameterListContext paramList = ctx.formalOrInferredParameterList();
|
||||
if (paramList != null) {
|
||||
paramList.accept(this);
|
||||
}
|
||||
|
||||
QLParser.BlockStatementsContext functionBlockStatements = ctx.blockStatements();
|
||||
if (functionBlockStatements != null) {
|
||||
push();
|
||||
functionBlockStatements.accept(this);
|
||||
pop();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void push() {
|
||||
this.existStack = this.existStack.push();
|
||||
}
|
||||
|
||||
public void pop() {
|
||||
this.existStack = this.existStack.pop();
|
||||
}
|
||||
|
||||
public ExistStack getStack() {
|
||||
return existStack;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
import com.alibaba.qlexpress4.runtime.operator.OperatorManager;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.antlr.v4.runtime.Parser;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.WritableToken;
|
||||
import org.antlr.v4.runtime.atn.DecisionInfo;
|
||||
import org.antlr.v4.runtime.atn.DecisionState;
|
||||
import org.antlr.v4.runtime.atn.PredictionMode;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class SyntaxTreeFactory {
|
||||
|
||||
private static final AtomicBoolean IS_WARM_UP = new AtomicBoolean();
|
||||
|
||||
public static void warmUp() {
|
||||
if (IS_WARM_UP.compareAndSet(false, true)) {
|
||||
// warm up
|
||||
warmUpExpress("1+1");
|
||||
warmUpExpress("a = b + c");
|
||||
}
|
||||
}
|
||||
|
||||
private static void warmUpExpress(String script) {
|
||||
buildTree(script, new OperatorManager(), false, false, s -> {
|
||||
}, InterpolationMode.SCRIPT, "${", "}", true);
|
||||
}
|
||||
|
||||
public static QLParser.ProgramContext buildTree(String script, ParserOperatorManager operatorManager,
|
||||
boolean printTree, boolean profile, Consumer<String> printer, InterpolationMode interpolationMode,
|
||||
String selectorStart, String selectorEnd, boolean strictNewLines) {
|
||||
QLexer lexer = new QLExtendLexer(CharStreams.fromString(script), script, interpolationMode, selectorStart,
|
||||
selectorEnd, strictNewLines);
|
||||
CommonTokenStream tokens = new CommonTokenStream(lexer);
|
||||
QLParser qlGrammarParser = new QLExtendParser(new AliasTokenStream(tokens, operatorManager), operatorManager,
|
||||
interpolationMode, strictNewLines);
|
||||
if (!printTree) {
|
||||
qlGrammarParser.removeErrorListeners();
|
||||
}
|
||||
qlGrammarParser.addErrorListener(new QLErrorListener(script));
|
||||
qlGrammarParser.setErrorHandler(new QLErrorStrategy());
|
||||
qlGrammarParser.getInterpreter().setPredictionMode(PredictionMode.SLL);
|
||||
if (profile) {
|
||||
qlGrammarParser.setProfile(true);
|
||||
}
|
||||
QLParser.ProgramContext programContext = qlGrammarParser.program();
|
||||
if (printTree) {
|
||||
printer.accept(tokens.getTokens().stream().map(Token::getText).collect(Collectors.joining(" | ")));
|
||||
printer.accept(programContext.toStringTree(qlGrammarParser));
|
||||
}
|
||||
if (profile) {
|
||||
profileParser(qlGrammarParser);
|
||||
}
|
||||
return programContext;
|
||||
}
|
||||
|
||||
private static void profileParser(Parser parser) {
|
||||
System.out.printf("%-" + 35 + "s", "rule");
|
||||
System.out.printf("%-" + 15 + "s", "time");
|
||||
System.out.printf("%-" + 15 + "s", "invocations");
|
||||
System.out.printf("%-" + 15 + "s", "lookahead");
|
||||
System.out.printf("%-" + 15 + "s", "lookahead(max)");
|
||||
System.out.printf("%-" + 15 + "s", "ambiguities");
|
||||
System.out.printf("%-" + 15 + "s", "errors");
|
||||
System.out.printf("%-" + 15 + "s", "fallBack");
|
||||
System.out.println();
|
||||
for (DecisionInfo decisionInfo : parser.getParseInfo().getDecisionInfo()) {
|
||||
DecisionState ds = parser.getATN().getDecisionState(decisionInfo.decision);
|
||||
String rule = parser.getRuleNames()[ds.ruleIndex];
|
||||
if (decisionInfo.timeInPrediction > 0) {
|
||||
System.out.printf("%-" + 35 + "s", rule);
|
||||
System.out.printf("%-" + 15 + "s", decisionInfo.timeInPrediction);
|
||||
System.out.printf("%-" + 15 + "s", decisionInfo.invocations);
|
||||
System.out.printf("%-" + 15 + "s", decisionInfo.SLL_TotalLook);
|
||||
System.out.printf("%-" + 15 + "s", decisionInfo.SLL_MaxLook);
|
||||
System.out.printf("%-" + 15 + "s", decisionInfo.ambiguities.size());
|
||||
System.out.printf("%-" + 15 + "s", decisionInfo.errors);
|
||||
System.out.printf("%-" + 15 + "s", decisionInfo.LL_Fallback);
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Token preHandleToken(Token originToken, ParserOperatorManager operatorManager) {
|
||||
if (originToken instanceof WritableToken && originToken.getType() == QLexer.ID) {
|
||||
Integer aliasId = operatorManager.getAlias(originToken.getText());
|
||||
if (aliasId != null && originToken.getType() != aliasId) {
|
||||
((WritableToken)originToken).setType(aliasId);
|
||||
}
|
||||
}
|
||||
return originToken;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
package com.alibaba.qlexpress4.aparser;
|
||||
|
||||
import com.alibaba.qlexpress4.aparser.QLParser.EmptyStatementContext;
|
||||
import com.alibaba.qlexpress4.aparser.QLParser.ExpressionContext;
|
||||
import com.alibaba.qlexpress4.aparser.QLParser.LocalVariableDeclarationStatementContext;
|
||||
import com.alibaba.qlexpress4.aparser.QLParser.PathPartContext;
|
||||
import com.alibaba.qlexpress4.aparser.QLParser.VarIdContext;
|
||||
import com.alibaba.qlexpress4.runtime.trace.TracePointTree;
|
||||
import com.alibaba.qlexpress4.runtime.trace.TraceType;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class TraceExpressionVisitor extends QLParserBaseVisitor<TracePointTree> {
|
||||
|
||||
private final List<TracePointTree> expressionTracePoints = new ArrayList<>();
|
||||
|
||||
public List<TracePointTree> getExpressionTracePoints() {
|
||||
return expressionTracePoints;
|
||||
}
|
||||
|
||||
// ==================== Statement ====================
|
||||
|
||||
@Override
|
||||
public TracePointTree visitThrowStatement(QLParser.ThrowStatementContext ctx) {
|
||||
TracePointTree tracePoint = newPoint(TraceType.STATEMENT, Collections.emptyList(), ctx.getStart());
|
||||
expressionTracePoints.add(tracePoint);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitLocalVariableDeclarationStatement(LocalVariableDeclarationStatementContext ctx) {
|
||||
TracePointTree tracePoint = newPoint(TraceType.STATEMENT, Collections.emptyList(), ctx.getStart());
|
||||
expressionTracePoints.add(tracePoint);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitExpressionStatement(QLParser.ExpressionStatementContext ctx) {
|
||||
TracePointTree expressionTrace = visitExpression(ctx.expression());
|
||||
expressionTracePoints.add(expressionTrace);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitWhileStatement(QLParser.WhileStatementContext ctx) {
|
||||
TracePointTree tracePoint = newPoint(TraceType.STATEMENT, Collections.emptyList(), ctx.getStart());
|
||||
expressionTracePoints.add(tracePoint);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitTraditionalForStatement(QLParser.TraditionalForStatementContext ctx) {
|
||||
TracePointTree tracePoint = newPoint(TraceType.STATEMENT, Collections.emptyList(), ctx.getStart());
|
||||
expressionTracePoints.add(tracePoint);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitForEachStatement(QLParser.ForEachStatementContext ctx) {
|
||||
TracePointTree tracePoint = newPoint(TraceType.STATEMENT, Collections.emptyList(), ctx.getStart());
|
||||
expressionTracePoints.add(tracePoint);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitFunctionStatement(QLParser.FunctionStatementContext ctx) {
|
||||
TracePointTree tracePoint =
|
||||
newPoint(TraceType.DEFINE_FUNCTION, Collections.emptyList(), ctx.varId().getStart());
|
||||
expressionTracePoints.add(tracePoint);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitMacroStatement(QLParser.MacroStatementContext ctx) {
|
||||
TracePointTree tracePoint = newPoint(TraceType.DEFINE_MACRO, Collections.emptyList(), ctx.varId().getStart());
|
||||
expressionTracePoints.add(tracePoint);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitBreakContinueStatement(QLParser.BreakContinueStatementContext ctx) {
|
||||
TracePointTree tracePoint = newPoint(TraceType.STATEMENT, Collections.emptyList(), ctx.getStart());
|
||||
expressionTracePoints.add(tracePoint);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitReturnStatement(QLParser.ReturnStatementContext ctx) {
|
||||
ExpressionContext returnExpressionContext = ctx.expression();
|
||||
if (returnExpressionContext != null) {
|
||||
TracePointTree returnExpressionTrace = returnExpressionContext.accept(this);
|
||||
TracePointTree tracePoint =
|
||||
newPoint(TraceType.RETURN, Collections.singletonList(returnExpressionTrace), ctx.getStart());
|
||||
expressionTracePoints.add(tracePoint);
|
||||
}
|
||||
else {
|
||||
TracePointTree tracePoint = newPoint(TraceType.RETURN, Collections.emptyList(), ctx.getStart());
|
||||
expressionTracePoints.add(tracePoint);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitEmptyStatement(QLParser.EmptyStatementContext ctx) {
|
||||
expressionTracePoints.add(newPoint(TraceType.STATEMENT, Collections.emptyList(), ctx.getStart()));
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitBlockStatements(QLParser.BlockStatementsContext ctx) {
|
||||
List<QLParser.BlockStatementContext> emptyChildren = ctx.blockStatement()
|
||||
.stream()
|
||||
.filter(bs -> bs instanceof EmptyStatementContext)
|
||||
.collect(Collectors.toList());
|
||||
if (emptyChildren.size() == ctx.blockStatement().size()) {
|
||||
// all emtpty
|
||||
emptyChildren.get(0).accept(this);
|
||||
return null;
|
||||
}
|
||||
|
||||
for (QLParser.BlockStatementContext blockStatementContext : ctx.blockStatement()) {
|
||||
if (!(blockStatementContext instanceof EmptyStatementContext)) {
|
||||
blockStatementContext.accept(this);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ==================== Expression ====================
|
||||
|
||||
@Override
|
||||
public TracePointTree visitExpression(QLParser.ExpressionContext ctx) {
|
||||
QLParser.TernaryExprContext ternaryExprContext = ctx.ternaryExpr();
|
||||
if (ternaryExprContext != null) {
|
||||
return visitTernaryExpr(ternaryExprContext);
|
||||
}
|
||||
|
||||
TracePointTree leftChildTree = visitLeftHandSide(ctx.leftHandSide());
|
||||
TracePointTree rightChildTree = visitExpression(ctx.expression());
|
||||
return newPoint(TraceType.OPERATOR,
|
||||
Arrays.asList(leftChildTree, rightChildTree),
|
||||
ctx.assignOperator().getStart());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitLeftHandSide(QLParser.LeftHandSideContext ctx) {
|
||||
VarIdContext varIdContext = ctx.varId();
|
||||
;
|
||||
List<PathPartContext> pathParts = ctx.pathPart();
|
||||
TracePointTree leftChildTree = null;
|
||||
int start = 0;
|
||||
if (!pathParts.isEmpty() && pathParts.get(0) instanceof QLParser.CallExprContext) {
|
||||
QLParser.CallExprContext callExprContext = (QLParser.CallExprContext)pathParts.get(0);
|
||||
leftChildTree = newPoint(TraceType.FUNCTION,
|
||||
traceArgumentList(callExprContext.argumentList()),
|
||||
varIdContext.getStart());
|
||||
start = 1;
|
||||
}
|
||||
else {
|
||||
leftChildTree = newPoint(TraceType.VARIABLE, Collections.emptyList(), ctx.getStart());
|
||||
}
|
||||
return pathParts(leftChildTree, pathParts.subList(start, pathParts.size()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitTernaryExpr(QLParser.TernaryExprContext ctx) {
|
||||
if (ctx.thenExpr == null) {
|
||||
return ctx.condition.accept(this);
|
||||
}
|
||||
|
||||
TracePointTree conditionPoint = ctx.condition.accept(this);
|
||||
TracePointTree thenPoint = ctx.thenExpr.accept(this);
|
||||
TracePointTree elsePoint = ctx.elseExpr.accept(this);
|
||||
|
||||
Token keyToken = ctx.QUESTION().getSymbol();
|
||||
return newPoint(TraceType.OPERATOR, Arrays.asList(conditionPoint, thenPoint, elsePoint), keyToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitBaseExpr(QLParser.BaseExprContext ctx) {
|
||||
TracePointTree leftChildTree = visitPrimary(ctx.primary());
|
||||
for (QLParser.LeftAssoContext leftAssoContext : ctx.leftAsso()) {
|
||||
Token keyToken = leftAssoContext.binaryop().getStart();
|
||||
TracePointTree rightChildTree = visitBaseExpr(leftAssoContext.baseExpr());
|
||||
leftChildTree = newPoint(TraceType.OPERATOR, Arrays.asList(leftChildTree, rightChildTree), keyToken);
|
||||
}
|
||||
return leftChildTree;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitPrimary(QLParser.PrimaryContext ctx) {
|
||||
TracePointTree leftChildTree = primaryBaseTrace(ctx);
|
||||
|
||||
// suffix
|
||||
QLParser.SuffixExpressContext suffixExpressContext = ctx.suffixExpress();
|
||||
if (suffixExpressContext != null) {
|
||||
leftChildTree =
|
||||
newPoint(TraceType.OPERATOR, Collections.singletonList(leftChildTree), suffixExpressContext.getStart());
|
||||
}
|
||||
|
||||
// prefix
|
||||
QLParser.PrefixExpressContext prefixExpressContext = ctx.prefixExpress();
|
||||
if (prefixExpressContext != null) {
|
||||
leftChildTree =
|
||||
newPoint(TraceType.OPERATOR, Collections.singletonList(leftChildTree), prefixExpressContext.getStart());
|
||||
}
|
||||
|
||||
return leftChildTree;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitConstExpr(QLParser.ConstExprContext ctx) {
|
||||
return newPoint(TraceType.VALUE, Collections.emptyList(), ctx.getText(), ctx.getStart());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitCastExpr(QLParser.CastExprContext ctx) {
|
||||
return ctx.primary().accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitGroupExpr(QLParser.GroupExprContext ctx) {
|
||||
return ctx.expression().accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitNewObjExpr(QLParser.NewObjExprContext ctx) {
|
||||
return newPoint(TraceType.PRIMARY, Collections.emptyList(), ctx.getText(), ctx.getStart());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitNewEmptyArrExpr(QLParser.NewEmptyArrExprContext ctx) {
|
||||
return newPoint(TraceType.PRIMARY, Collections.emptyList(), ctx.getText(), ctx.getStart());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitNewInitArrExpr(QLParser.NewInitArrExprContext ctx) {
|
||||
return newPoint(TraceType.PRIMARY, Collections.emptyList(), ctx.getText(), ctx.getStart());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitLambdaExpr(QLParser.LambdaExprContext ctx) {
|
||||
return newPoint(TraceType.PRIMARY, Collections.emptyList(), ctx.ARROW().getSymbol());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitVarIdExpr(QLParser.VarIdExprContext ctx) {
|
||||
return newPoint(TraceType.VARIABLE, Collections.emptyList(), ctx.getStart());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitTypeExpr(QLParser.TypeExprContext ctx) {
|
||||
return newPoint(TraceType.VALUE, Collections.emptyList(), ctx.getStart());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitListExpr(QLParser.ListExprContext ctx) {
|
||||
QLParser.ListItemsContext listItemsContext = ctx.listItems();
|
||||
if (listItemsContext == null) {
|
||||
return newPoint(TraceType.LIST, Collections.emptyList(), ctx.getStart());
|
||||
}
|
||||
List<TracePointTree> children = listItemsContext.expression()
|
||||
.stream()
|
||||
.map(expression -> expression.accept(this))
|
||||
.collect(Collectors.toList());
|
||||
return newPoint(TraceType.LIST, children, ctx.getStart());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitMapExpr(QLParser.MapExprContext ctx) {
|
||||
return newPoint(TraceType.MAP, Collections.emptyList(), ctx.getStart());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitBlockExpr(QLParser.BlockExprContext ctx) {
|
||||
TraceExpressionVisitor traceExpressionVisitor = new TraceExpressionVisitor();
|
||||
ctx.blockStatements().accept(traceExpressionVisitor);
|
||||
List<TracePointTree> children = traceExpressionVisitor.getExpressionTracePoints();
|
||||
return newPoint(TraceType.BLOCK, children, ctx.getStart());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitQlIf(QLParser.QlIfContext ctx) {
|
||||
List<TracePointTree> children = new ArrayList<>(3);
|
||||
children.add(ctx.condition.accept(this));
|
||||
// thenBody
|
||||
children.add(visitThenBody(ctx.thenBody()));
|
||||
// elseBody
|
||||
if (ctx.elseBody() != null) {
|
||||
children.add(visitElseBody(ctx.elseBody()));
|
||||
}
|
||||
return newPoint(TraceType.IF, children, "if", ctx.getStart());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitThenBody(QLParser.ThenBodyContext thenBody) {
|
||||
if (thenBody.blockStatements() != null) {
|
||||
TraceExpressionVisitor blockVisitor = new TraceExpressionVisitor();
|
||||
thenBody.blockStatements().accept(blockVisitor);
|
||||
return newPoint(TraceType.BLOCK, blockVisitor.getExpressionTracePoints(), thenBody.getStart());
|
||||
}
|
||||
else if (thenBody.expression() != null) {
|
||||
return thenBody.expression().accept(this);
|
||||
}
|
||||
else if (thenBody.blockStatement() != null) {
|
||||
TraceExpressionVisitor blockVisitor = new TraceExpressionVisitor();
|
||||
thenBody.blockStatement().accept(blockVisitor);
|
||||
return blockVisitor.getExpressionTracePoints().get(0);
|
||||
}
|
||||
return newPoint(TraceType.BLOCK, Collections.emptyList(), thenBody.getStart());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitElseBody(QLParser.ElseBodyContext elseBody) {
|
||||
if (elseBody.blockStatements() != null) {
|
||||
TraceExpressionVisitor blockVisitor = new TraceExpressionVisitor();
|
||||
elseBody.blockStatements().accept(blockVisitor);
|
||||
return newPoint(TraceType.BLOCK, blockVisitor.getExpressionTracePoints(), elseBody.getStart());
|
||||
}
|
||||
else if (elseBody.expression() != null) {
|
||||
return elseBody.expression().accept(this);
|
||||
}
|
||||
else if (elseBody.blockStatement() != null) {
|
||||
TraceExpressionVisitor blockVisitor = new TraceExpressionVisitor();
|
||||
elseBody.blockStatement().accept(blockVisitor);
|
||||
return blockVisitor.getExpressionTracePoints().get(0);
|
||||
}
|
||||
else if (elseBody.qlIf() != null) {
|
||||
return elseBody.qlIf().accept(this);
|
||||
}
|
||||
return newPoint(TraceType.BLOCK, Collections.emptyList(), elseBody.getStart());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitTryCatchExpr(QLParser.TryCatchExprContext ctx) {
|
||||
return newPoint(TraceType.PRIMARY, Collections.emptyList(), ctx.getStart());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePointTree visitContextSelectExpr(QLParser.ContextSelectExprContext ctx) {
|
||||
return newPoint(TraceType.PRIMARY, Collections.emptyList(), ctx.getStart());
|
||||
}
|
||||
|
||||
// ==================== Private Helper ====================
|
||||
|
||||
private TracePointTree primaryBaseTrace(QLParser.PrimaryContext ctx) {
|
||||
QLParser.PrimaryNoFixNonPathableContext primaryNoFixNonPathableContext = ctx.primaryNoFixNonPathable();
|
||||
if (primaryNoFixNonPathableContext != null) {
|
||||
return primaryNoFixNonPathableContext.accept(this);
|
||||
}
|
||||
|
||||
QLParser.PrimaryNoFixPathableContext primaryNoFixPathableContext = ctx.primaryNoFixPathable();
|
||||
List<QLParser.PathPartContext> pathPartContexts = ctx.pathPart();
|
||||
TracePointTree leftChildTree = null;
|
||||
int start = 0;
|
||||
if (primaryNoFixPathableContext instanceof QLParser.VarIdExprContext && !pathPartContexts.isEmpty()
|
||||
&& pathPartContexts.get(0) instanceof QLParser.CallExprContext) {
|
||||
// function call
|
||||
QLParser.VarIdExprContext functionNameContext = (QLParser.VarIdExprContext)primaryNoFixPathableContext;
|
||||
QLParser.CallExprContext callExprContext = (QLParser.CallExprContext)pathPartContexts.get(0);
|
||||
leftChildTree = newPoint(TraceType.FUNCTION,
|
||||
traceArgumentList(callExprContext.argumentList()),
|
||||
functionNameContext.getStart());
|
||||
start = 1;
|
||||
}
|
||||
else {
|
||||
leftChildTree = primaryNoFixPathableContext.accept(this);
|
||||
}
|
||||
|
||||
return pathParts(leftChildTree, pathPartContexts.subList(start, pathPartContexts.size()));
|
||||
}
|
||||
|
||||
private TracePointTree pathParts(TracePointTree pathRoot, List<QLParser.PathPartContext> pathPartContexts) {
|
||||
TracePointTree leftChildTree = pathRoot;
|
||||
for (QLParser.PathPartContext current : pathPartContexts) {
|
||||
if (current instanceof QLParser.MethodInvokeContext
|
||||
|| current instanceof QLParser.OptionalMethodInvokeContext
|
||||
|| current instanceof QLParser.SpreadMethodInvokeContext) {
|
||||
QLParser.ArgumentListContext argumentList = current.getChild(QLParser.ArgumentListContext.class, 0);
|
||||
List<TracePointTree> argumentsChildren = traceArgumentList(argumentList);
|
||||
List<TracePointTree> methodChildren = new ArrayList<>(1 + argumentsChildren.size());
|
||||
methodChildren.add(leftChildTree);
|
||||
methodChildren.addAll(argumentsChildren);
|
||||
Token keyToken = current.getChild(QLParser.VarIdContext.class, 0).getStart();
|
||||
leftChildTree = newPoint(TraceType.METHOD, methodChildren, keyToken);
|
||||
}
|
||||
else if (current instanceof QLParser.CallExprContext) {
|
||||
QLParser.ArgumentListContext argumentList = current.getChild(QLParser.ArgumentListContext.class, 0);
|
||||
List<TracePointTree> argumentsChildren = traceArgumentList(argumentList);
|
||||
List<TracePointTree> callChildren = new ArrayList<>(1 + argumentsChildren.size());
|
||||
callChildren.add(leftChildTree);
|
||||
callChildren.addAll(argumentsChildren);
|
||||
leftChildTree = newPoint(TraceType.OPERATOR, callChildren, current.getStart());
|
||||
}
|
||||
else if (current instanceof QLParser.IndexExprContext) {
|
||||
QLParser.IndexValueExprContext indexValueExprContext =
|
||||
current.getChild(QLParser.IndexValueExprContext.class, 0);
|
||||
List<TracePointTree> indexArgChildren =
|
||||
indexValueExprContext.getRuleContexts(QLParser.ExpressionContext.class)
|
||||
.stream()
|
||||
.map(expression -> expression.accept(this))
|
||||
.collect(Collectors.toList());
|
||||
List<TracePointTree> indexChildren = new ArrayList<>(1 + indexArgChildren.size());
|
||||
indexChildren.add(leftChildTree);
|
||||
indexChildren.addAll(indexArgChildren);
|
||||
leftChildTree = newPoint(TraceType.OPERATOR, indexChildren, current.getStart());
|
||||
}
|
||||
else {
|
||||
// field
|
||||
leftChildTree = newPoint(TraceType.FIELD, Collections.singletonList(leftChildTree), current.getStop());
|
||||
}
|
||||
}
|
||||
return leftChildTree;
|
||||
}
|
||||
|
||||
private List<TracePointTree> traceArgumentList(QLParser.ArgumentListContext argumentListContext) {
|
||||
if (argumentListContext == null || argumentListContext.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return argumentListContext.expression()
|
||||
.stream()
|
||||
.map(expression -> expression.accept(this))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private TracePointTree newPoint(TraceType traceType, List<TracePointTree> children, Token keyToken) {
|
||||
return new TracePointTree(traceType, keyToken.getText(), children, keyToken.getLine(),
|
||||
keyToken.getCharPositionInLine(), keyToken.getStartIndex());
|
||||
}
|
||||
|
||||
private TracePointTree newPoint(TraceType traceType, List<TracePointTree> children, String text, Token keyToken) {
|
||||
return new TracePointTree(traceType, text, children, keyToken.getLine(), keyToken.getCharPositionInLine(),
|
||||
keyToken.getStartIndex());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.alibaba.qlexpress4.aparser.compiletimefunction;
|
||||
|
||||
import com.alibaba.qlexpress4.aparser.QLParser;
|
||||
import com.alibaba.qlexpress4.exception.ErrorReporter;
|
||||
import com.alibaba.qlexpress4.exception.QLSyntaxException;
|
||||
import com.alibaba.qlexpress4.runtime.QLambdaDefinition;
|
||||
import com.alibaba.qlexpress4.runtime.QLambdaDefinitionInner;
|
||||
import com.alibaba.qlexpress4.runtime.instruction.QLInstruction;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public interface CodeGenerator {
|
||||
|
||||
void addInstruction(QLInstruction qlInstruction);
|
||||
|
||||
void addInstructionsByTree(ParseTree tree);
|
||||
|
||||
QLSyntaxException reportParseErr(String errCode, String errReason);
|
||||
|
||||
QLambdaDefinition generateLambdaDefinition(QLParser.ExpressionContext expressionContext,
|
||||
List<QLambdaDefinitionInner.Param> params);
|
||||
|
||||
ErrorReporter getErrorReporter();
|
||||
|
||||
ErrorReporter newReporterWithToken(Token token);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.alibaba.qlexpress4.aparser.compiletimefunction;
|
||||
|
||||
import com.alibaba.qlexpress4.aparser.OperatorFactory;
|
||||
import com.alibaba.qlexpress4.aparser.QLParser;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public interface CompileTimeFunction {
|
||||
|
||||
/**
|
||||
* create instructions for function in compile time
|
||||
* @param functionName function name
|
||||
* @param arguments arguments syntax tree
|
||||
* @param operatorFactory operator factory
|
||||
* @param codeGenerator tool for code generate
|
||||
*/
|
||||
void createFunctionInstruction(String functionName, List<QLParser.ExpressionContext> arguments,
|
||||
OperatorFactory operatorFactory, CodeGenerator codeGenerator);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.alibaba.qlexpress4.api;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class BatchAddFunctionResult {
|
||||
|
||||
private final List<String> succ;
|
||||
|
||||
private final List<String> fail;
|
||||
|
||||
public BatchAddFunctionResult() {
|
||||
this.succ = new ArrayList<>();
|
||||
this.fail = new ArrayList<>();
|
||||
}
|
||||
|
||||
public List<String> getSucc() {
|
||||
return succ;
|
||||
}
|
||||
|
||||
public List<String> getFail() {
|
||||
return fail;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.alibaba.qlexpress4.api;
|
||||
|
||||
/**
|
||||
* Author: TaoKan
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface QLFunctionalVarargs {
|
||||
Object call(Object... params);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.alibaba.qlexpress4.enums;
|
||||
|
||||
/**
|
||||
* Author: TaoKan
|
||||
*/
|
||||
public enum AccessMode {
|
||||
WRITE, READ,
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.alibaba.qlexpress4.exception;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class DefaultErrReporter implements ErrorReporter {
|
||||
|
||||
private final String script;
|
||||
|
||||
private final int tokenStartPos;
|
||||
|
||||
private final int line;
|
||||
|
||||
private final int col;
|
||||
|
||||
private final String lexeme;
|
||||
|
||||
public DefaultErrReporter(String script, int tokenStartPos, int line, int col, String lexeme) {
|
||||
this.script = script;
|
||||
this.tokenStartPos = tokenStartPos;
|
||||
this.line = line;
|
||||
this.col = col;
|
||||
this.lexeme = lexeme;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QLRuntimeException reportFormatWithCatch(Object catchObj, String errorCode, String format, Object... args) {
|
||||
return QLException.reportRuntimeErrWithAttach(script,
|
||||
tokenStartPos,
|
||||
line,
|
||||
col,
|
||||
lexeme,
|
||||
errorCode,
|
||||
String.format(format, args),
|
||||
catchObj);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.alibaba.qlexpress4.exception;
|
||||
|
||||
public interface ErrorReporter {
|
||||
|
||||
default QLRuntimeException report(Object catchObj, String errorCode, String reason) {
|
||||
return reportFormatWithCatch(catchObj, errorCode, reason);
|
||||
}
|
||||
|
||||
default QLRuntimeException report(String errorCode, String reason) {
|
||||
return reportFormatWithCatch(null, errorCode, reason);
|
||||
}
|
||||
|
||||
default QLRuntimeException reportFormat(String errorCode, String format, Object... args) {
|
||||
return reportFormatWithCatch(null, errorCode, format, args);
|
||||
}
|
||||
|
||||
QLRuntimeException reportFormatWithCatch(Object catchObj, String errorCode, String format, Object... args);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.alibaba.qlexpress4.exception;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class ExMessageUtil {
|
||||
|
||||
private static final String REPORT_TEMPLATE = "[Error {0}: {1}]\n[Near: {2}]\n{3}\n[Line: {4}, Column: {5}]";
|
||||
|
||||
private static final int SNIPPET_EXTENSION_LEN = 20;
|
||||
|
||||
public static class ExMessage {
|
||||
private final String message;
|
||||
|
||||
private final String snippet;
|
||||
|
||||
public ExMessage(String message, String snippet) {
|
||||
this.message = message;
|
||||
this.snippet = snippet;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public String getSnippet() {
|
||||
return snippet;
|
||||
}
|
||||
}
|
||||
|
||||
public static ExMessage format(String script, int tokenStartPos, int tokenLine, int tokenCol, String lexeme,
|
||||
String errorCode, String reason) {
|
||||
int startReportPos = Math.max(tokenStartPos - SNIPPET_EXTENSION_LEN, 0);
|
||||
int endReportPos = Math.min(tokenStartPos + lexeme.length() + SNIPPET_EXTENSION_LEN, script.length());
|
||||
|
||||
StringBuilder snippetBuilder = new StringBuilder();
|
||||
if (startReportPos > 0) {
|
||||
snippetBuilder.append("...");
|
||||
}
|
||||
for (int i = startReportPos; i < endReportPos; i++) {
|
||||
char codeChar = script.charAt(i);
|
||||
snippetBuilder.append(codeChar < ' ' ? ' ' : codeChar);
|
||||
}
|
||||
if (endReportPos < script.length()) {
|
||||
snippetBuilder.append("...");
|
||||
}
|
||||
|
||||
StringBuilder carteBuilder = new StringBuilder().append(" ");
|
||||
if (startReportPos > 0) {
|
||||
carteBuilder.append(" ");
|
||||
}
|
||||
for (int i = startReportPos; i < tokenStartPos; i++) {
|
||||
carteBuilder.append(' ');
|
||||
}
|
||||
for (int i = 0; i < lexeme.length(); i++) {
|
||||
carteBuilder.append('^');
|
||||
}
|
||||
|
||||
String snippet = snippetBuilder.toString();
|
||||
String message = MessageFormat
|
||||
.format(REPORT_TEMPLATE, errorCode, reason, snippet, carteBuilder.toString(), tokenLine, tokenCol);
|
||||
return new ExMessage(message, snippet);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.alibaba.qlexpress4.exception;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public interface ExceptionFactory<T> {
|
||||
|
||||
T newException(String message, int lineNo, int colNo, String errLexeme, String errorCode, String reason,
|
||||
String snippet);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.alibaba.qlexpress4.exception;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class PureErrReporter implements ErrorReporter {
|
||||
|
||||
public static PureErrReporter INSTANCE = new PureErrReporter();
|
||||
|
||||
private PureErrReporter() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public QLRuntimeException reportFormatWithCatch(Object catchObj, String errorCode, String format, Object... args) {
|
||||
return new QLRuntimeException(null, String.format(format, args), errorCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.alibaba.qlexpress4.exception;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public enum QLErrorCodes {
|
||||
// @formatter:off
|
||||
// syntax error
|
||||
SYNTAX_ERROR(""),
|
||||
MISSING_INDEX("missing index expression"),
|
||||
INVALID_NUMBER("invalid number"),
|
||||
CLASS_NOT_FOUND("can not find class: %s"),
|
||||
|
||||
// runtime error
|
||||
INVALID_INDEX("index can only be number"),
|
||||
INDEX_OUT_BOUND("index out of bound"),
|
||||
NONINDEXABLE_OBJECT("object of class %s is not indexable"),
|
||||
NONTRAVERSABLE_OBJECT("object of class %s is not traversable"),
|
||||
NULL_FIELD_ACCESS("can not access field from null"),
|
||||
NULL_METHOD_ACCESS("can not access method from null"),
|
||||
FIELD_NOT_FOUND("'%s' field not found"),
|
||||
SET_FIELD_UNKNOWN_ERROR("unknown error when setting field '%s' value"),
|
||||
GET_FIELD_UNKNOWN_ERROR("unknown error when getting field '%s' value"),
|
||||
INVOKE_METHOD_WITH_WRONG_ARGUMENTS("invoke method '%s' with wrong arguments"),
|
||||
INVOKE_METHOD_INNER_ERROR("exception from inner when invoking method '%s'"),
|
||||
INVOKE_METHOD_UNKNOWN_ERROR("unknown error when invoking method '%s'"),
|
||||
INVOKE_FUNCTION_INNER_ERROR("exception from inner when invoking function '%s', error message: %s"),
|
||||
FUNCTION_NOT_FOUND("function '%s' not found"),
|
||||
FUNCTION_TYPE_MISMATCH("symbol '%s' is not a function type"),
|
||||
INVOKE_LAMBDA_ERROR("error when invoking lambda"),
|
||||
NULL_CALL("can not call null"),
|
||||
OBJECT_NOT_CALLABLE("type '%s' is not callable"),
|
||||
METHOD_NOT_FOUND("no suitable method '%s' found for args %s"),
|
||||
INVOKE_CONSTRUCTOR_UNKNOWN_ERROR("unknown error when invoking constructor"),
|
||||
INVOKE_CONSTRUCTOR_INNER_ERROR("exception from inner when invoking constructor"),
|
||||
NO_SUITABLE_CONSTRUCTOR("no suitable constructor for types %s"),
|
||||
EXECUTE_BLOCK_ERROR("error when executing block"),
|
||||
INCOMPATIBLE_TYPE_CAST("incompatible cast from type: %s to type: %s"),
|
||||
INVALID_CAST_TARGET("target for type cast must be a class, but accept %s"),
|
||||
SCRIPT_TIME_OUT("script exceeds timeout milliseconds, which is %d ms"),
|
||||
INCOMPATIBLE_ASSIGNMENT_TYPE("variable declared type %s, assigned with incompatible value type %s"),
|
||||
FOR_EACH_ITERABLE_REQUIRED("for-each can only be applied to iterable"),
|
||||
FOR_EACH_TYPE_MISMATCH("for-each type mismatch, required %s, but %s provided"),
|
||||
FOR_EACH_UNKNOWN_ERROR("unknown error when executing for-each"),
|
||||
FOR_INIT_ERROR("error when executing for init"),
|
||||
FOR_BODY_ERROR("error when executing for body"),
|
||||
FOR_UPDATE_ERROR("error when executing for update"),
|
||||
FOR_CONDITION_ERROR("error when executing for condition"),
|
||||
FOR_CONDITION_BOOL_REQUIRED("result of for condition must be bool"),
|
||||
WHILE_CONDITION_BOOL_REQUIRED("result of while condition must be bool"),
|
||||
WHILE_CONDITION_ERROR("error when executing while condition"),
|
||||
CONDITION_BOOL_REQUIRED("result of condition expression must be bool"),
|
||||
ARRAY_SIZE_NUM_REQUIRED("size of array must be number"),
|
||||
EXCEED_MAX_ARR_LENGTH("array length %d, exceed max allowed length %d"),
|
||||
INCOMPATIBLE_ARRAY_ITEM_TYPE("item %d with type %s incompatible with array type %s"),
|
||||
INVALID_ASSIGNMENT("value %s is not assignable"),
|
||||
EXECUTE_OPERATOR_EXCEPTION("exception when executing '%s %s %s'"),
|
||||
INVALID_ARITHMETIC(""),
|
||||
INVALID_BINARY_OPERAND("the '%s' operator can not be applied to leftType:%s with leftValue:%s and rightType:%s with rightValue:%s"),
|
||||
INVALID_UNARY_OPERAND("the '%s' operator can not be applied to type %s with value %s"),
|
||||
EXECUTE_FINAL_BLOCK_ERROR("error when executing final block in try...catch...final..."),
|
||||
EXECUTE_TRY_BLOCK_ERROR("error when executing try... block"),
|
||||
EXECUTE_CATCH_HANDLER_ERROR("error when executing handler of '%s'"),
|
||||
|
||||
// operator restriction error
|
||||
OPERATOR_NOT_ALLOWED("Script uses disallowed operator: %s"),
|
||||
|
||||
// user defined exception
|
||||
INVALID_ARGUMENT(""),
|
||||
BIZ_EXCEPTION(""),
|
||||
QL_THROW("qlexpress throw statement");
|
||||
// @formatter:on
|
||||
|
||||
private final String errorMsg;
|
||||
|
||||
QLErrorCodes(String errorMsg) {
|
||||
this.errorMsg = errorMsg;
|
||||
}
|
||||
|
||||
public String getErrorMsg() {
|
||||
return errorMsg;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.alibaba.qlexpress4.exception;
|
||||
|
||||
import com.alibaba.qlexpress4.exception.lsp.Diagnostic;
|
||||
import com.alibaba.qlexpress4.exception.lsp.Position;
|
||||
import com.alibaba.qlexpress4.exception.lsp.Range;
|
||||
|
||||
public class QLException extends RuntimeException {
|
||||
|
||||
private final Diagnostic diagnostic;
|
||||
|
||||
protected QLException(String message, Diagnostic diagnostic) {
|
||||
super(message);
|
||||
this.diagnostic = diagnostic;
|
||||
}
|
||||
|
||||
public Diagnostic getDiagnostic() {
|
||||
return diagnostic;
|
||||
}
|
||||
|
||||
public int getPos() {
|
||||
return diagnostic.getPos();
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return diagnostic.getMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return line no based 1
|
||||
*/
|
||||
public int getLineNo() {
|
||||
return diagnostic.getRange().getStart().getLine() + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return col no based 1
|
||||
*/
|
||||
public int getColNo() {
|
||||
return diagnostic.getRange().getStart().getCharacter() + 1;
|
||||
}
|
||||
|
||||
public String getErrLexeme() {
|
||||
return diagnostic.getLexeme();
|
||||
}
|
||||
|
||||
public String getErrorCode() {
|
||||
return diagnostic.getCode();
|
||||
}
|
||||
|
||||
public static QLSyntaxException reportScannerErr(String script, int tokenStartPos, int line, int col, String lexeme,
|
||||
String errorCode, String reason) {
|
||||
ExMessageUtil.ExMessage exMessage =
|
||||
ExMessageUtil.format(script, tokenStartPos, line, col, lexeme, errorCode, reason);
|
||||
Diagnostic diagnostic =
|
||||
toDiagnostic(tokenStartPos, line, col, lexeme, errorCode, reason, exMessage.getSnippet());
|
||||
return new QLSyntaxException(exMessage.getMessage(), diagnostic);
|
||||
}
|
||||
|
||||
public static QLRuntimeException reportRuntimeErrWithAttach(String script, int tokenStartPos, int line, int col,
|
||||
String lexeme, String errorCode, String reason, Object catchObj) {
|
||||
ExMessageUtil.ExMessage exMessage =
|
||||
ExMessageUtil.format(script, tokenStartPos, line, col, lexeme, errorCode, reason);
|
||||
Diagnostic diagnostic =
|
||||
toDiagnostic(tokenStartPos, line, col, lexeme, errorCode, reason, exMessage.getSnippet());
|
||||
return errorCode.equals(QLErrorCodes.SCRIPT_TIME_OUT.name())
|
||||
? new QLTimeoutException(catchObj, exMessage.getMessage(), diagnostic)
|
||||
: new QLRuntimeException(catchObj, exMessage.getMessage(), diagnostic);
|
||||
}
|
||||
|
||||
private static Diagnostic toDiagnostic(int startPos, int line, int col, String lexeme, String errorCode,
|
||||
String reason, String snippet) {
|
||||
int zeroBasedLine = line - 1;
|
||||
int zeroBasedCol = col - 1;
|
||||
Position start = new Position(zeroBasedLine, zeroBasedCol);
|
||||
Position end = new Position(zeroBasedLine, zeroBasedCol + lexeme.length());
|
||||
Range range = new Range(start, end);
|
||||
return new Diagnostic(startPos, range, lexeme, errorCode, reason, snippet);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.alibaba.qlexpress4.exception;
|
||||
|
||||
import com.alibaba.qlexpress4.exception.lsp.Diagnostic;
|
||||
import com.alibaba.qlexpress4.exception.lsp.Range;
|
||||
|
||||
public class QLRuntimeException extends QLException {
|
||||
|
||||
/**
|
||||
* catchObj can be catched at QLExpress catch clause
|
||||
*/
|
||||
private final Object catchObj;
|
||||
|
||||
/*
|
||||
* Visible for test
|
||||
*/
|
||||
protected QLRuntimeException(Object catchObj, String reason, String errorCode) {
|
||||
super("", new Diagnostic(0, new Range(null, null), "", errorCode, reason, ""));
|
||||
this.catchObj = catchObj;
|
||||
}
|
||||
|
||||
protected QLRuntimeException(Object catchObj, String message, Diagnostic diagnostic) {
|
||||
super(message, diagnostic);
|
||||
this.catchObj = catchObj;
|
||||
if (catchObj instanceof Throwable) {
|
||||
super.initCause((Throwable)catchObj);
|
||||
}
|
||||
}
|
||||
|
||||
public Object getCatchObj() {
|
||||
return catchObj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.alibaba.qlexpress4.exception;
|
||||
|
||||
import com.alibaba.qlexpress4.exception.lsp.Diagnostic;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class QLSyntaxException extends QLException {
|
||||
protected QLSyntaxException(String message, Diagnostic diagnostic) {
|
||||
super(message, diagnostic);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.alibaba.qlexpress4.exception;
|
||||
|
||||
import com.alibaba.qlexpress4.exception.lsp.Diagnostic;
|
||||
|
||||
public class QLTimeoutException extends QLRuntimeException {
|
||||
protected QLTimeoutException(Object catchObj, String reason, String errorCode) {
|
||||
super(catchObj, reason, errorCode);
|
||||
}
|
||||
|
||||
protected QLTimeoutException(Object catchObj, String message, Diagnostic diagnostic) {
|
||||
super(catchObj, message, diagnostic);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.alibaba.qlexpress4.exception;
|
||||
|
||||
/**
|
||||
* user define error message for custom function/operator
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class UserDefineException extends Exception {
|
||||
|
||||
public enum ExceptionType {
|
||||
INVALID_ARGUMENT, BIZ_EXCEPTION
|
||||
};
|
||||
|
||||
private final ExceptionType type;
|
||||
|
||||
public UserDefineException(String message) {
|
||||
this(ExceptionType.BIZ_EXCEPTION, message);
|
||||
}
|
||||
|
||||
public UserDefineException(ExceptionType type, String message) {
|
||||
super(message);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public ExceptionType getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.alibaba.qlexpress4.exception.lsp;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class Diagnostic {
|
||||
|
||||
/**
|
||||
* start position in script
|
||||
*/
|
||||
private final int pos;
|
||||
|
||||
/**
|
||||
* The range at which the message applies.
|
||||
*/
|
||||
private final Range range;
|
||||
|
||||
/**
|
||||
* The diagnostic's code, which might appear in the user interface.
|
||||
*/
|
||||
private final String code;
|
||||
|
||||
/**
|
||||
* The diagnostic's message.
|
||||
*/
|
||||
private final String message;
|
||||
|
||||
/**
|
||||
* snippet near error position
|
||||
*/
|
||||
private final String snippet;
|
||||
|
||||
/**
|
||||
* lexeme in range
|
||||
*/
|
||||
private final String lexeme;
|
||||
|
||||
public Diagnostic(int pos, Range range, String lexeme, String code, String message, String snippet) {
|
||||
this.pos = pos;
|
||||
this.range = range;
|
||||
this.lexeme = lexeme;
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.snippet = snippet;
|
||||
}
|
||||
|
||||
public int getPos() {
|
||||
return pos;
|
||||
}
|
||||
|
||||
public Range getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
public String getLexeme() {
|
||||
return lexeme;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public String getSnippet() {
|
||||
return snippet;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.alibaba.qlexpress4.exception.lsp;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class Position {
|
||||
|
||||
/**
|
||||
* Line position in a document (zero-based).
|
||||
*/
|
||||
private final int line;
|
||||
|
||||
/**
|
||||
* Character offset on a line in a document (zero-based).
|
||||
* If the character value is greater than the line length it defaults back
|
||||
* to the line length.
|
||||
*/
|
||||
private final int character;
|
||||
|
||||
public Position(int line, int character) {
|
||||
this.line = line;
|
||||
this.character = character;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
return line;
|
||||
}
|
||||
|
||||
public int getCharacter() {
|
||||
return character;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.alibaba.qlexpress4.exception.lsp;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class Range {
|
||||
|
||||
private final Position start;
|
||||
|
||||
private final Position end;
|
||||
|
||||
public Range(Position start, Position end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public Position getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public Position getEnd() {
|
||||
return end;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.alibaba.qlexpress4.member;
|
||||
|
||||
import com.alibaba.qlexpress4.annotation.QLAlias;
|
||||
import com.alibaba.qlexpress4.utils.QLAliasUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Author: TaoKan
|
||||
*/
|
||||
public class FieldHandler {
|
||||
public static class Preferred {
|
||||
|
||||
public static String preHandleAlias(Class<?> baseClass, String propertyName) {
|
||||
Field[] fields = baseClass.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
if (QLAliasUtils.matchQLAlias(propertyName, field.getAnnotationsByType(QLAlias.class))) {
|
||||
return field.getName();
|
||||
}
|
||||
}
|
||||
Class<?> superclass = baseClass.getSuperclass();
|
||||
if (superclass != null) {
|
||||
return preHandleAlias(superclass, propertyName);
|
||||
}
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
public static Field gatherFieldRecursive(Class<?> baseClass, String propertyName) {
|
||||
Field[] fields = baseClass.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
if (propertyName.equals(field.getName())) {
|
||||
return field;
|
||||
}
|
||||
if (QLAliasUtils.matchQLAlias(propertyName, field.getAnnotationsByType(QLAlias.class))) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
Class<?> superclass = baseClass.getSuperclass();
|
||||
if (superclass != null) {
|
||||
return gatherFieldRecursive(superclass, propertyName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
106
src/main/java/com/alibaba/qlexpress4/member/MethodHandler.java
Normal file
106
src/main/java/com/alibaba/qlexpress4/member/MethodHandler.java
Normal file
@@ -0,0 +1,106 @@
|
||||
package com.alibaba.qlexpress4.member;
|
||||
|
||||
import com.alibaba.qlexpress4.runtime.IMethod;
|
||||
import com.alibaba.qlexpress4.utils.BasicUtil;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* Author: TaoKan
|
||||
*/
|
||||
public class MethodHandler {
|
||||
|
||||
public static Method getGetter(Class<?> clazz, String property) {
|
||||
String isGet = BasicUtil.getIsGetter(property);
|
||||
String getter = BasicUtil.getGetter(property);
|
||||
GetterCandidateMethod mGetCandidate = null;
|
||||
for (Method method : clazz.getMethods()) {
|
||||
if ((isGet.equals(method.getName())) && method.getReturnType() == boolean.class
|
||||
&& BasicUtil.isPublic(method) && method.getParameterTypes().length == 0) {
|
||||
GetterCandidateMethod isGetMethod = new GetterCandidateMethod(method, 2);
|
||||
if (isPreferredGetter(mGetCandidate, isGetMethod)) {
|
||||
mGetCandidate = isGetMethod;
|
||||
}
|
||||
}
|
||||
else if (getter.equals(method.getName()) && BasicUtil.isPublic(method)
|
||||
&& method.getParameterTypes().length == 0) {
|
||||
GetterCandidateMethod getterMethod = new GetterCandidateMethod(method, 1);
|
||||
if (isPreferredGetter(mGetCandidate, getterMethod)) {
|
||||
mGetCandidate = getterMethod;
|
||||
}
|
||||
}
|
||||
}
|
||||
return mGetCandidate == null ? null : mGetCandidate.getMethod();
|
||||
}
|
||||
|
||||
public static boolean isPreferredGetter(GetterCandidateMethod before, GetterCandidateMethod after) {
|
||||
if (before == null) {
|
||||
return true;
|
||||
}
|
||||
return after.getPriority() >= before.getPriority();
|
||||
}
|
||||
|
||||
public static Method getSetter(Class<?> clazz, String property) {
|
||||
property = BasicUtil.getSetter(property);
|
||||
|
||||
for (Method method : clazz.getMethods()) {
|
||||
if (BasicUtil.isPublic(method) && method.getParameterTypes().length == 1
|
||||
&& property.equals(method.getName())) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean hasOnlyOneAbstractMethod(Method[] methods) {
|
||||
int count = 0;
|
||||
for (Method method : methods) {
|
||||
if (Modifier.isAbstract(method.getModifiers())) {
|
||||
count++;
|
||||
if (count > 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count == 1;
|
||||
}
|
||||
|
||||
public static class Access {
|
||||
public static Object accessMethodValue(IMethod method, Object bean, Object[] args)
|
||||
throws IllegalArgumentException, InvocationTargetException, IllegalAccessException {
|
||||
if (!method.isAccess()) {
|
||||
method.setAccessible(true);
|
||||
}
|
||||
return method.invoke(bean, args);
|
||||
}
|
||||
}
|
||||
|
||||
static class GetterCandidateMethod {
|
||||
public Method getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public void setMethod(Method method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
private Method method;
|
||||
|
||||
public int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
public void setPriority(int priority) {
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
private int priority;
|
||||
|
||||
public GetterCandidateMethod(Method method, int priority) {
|
||||
this.method = method;
|
||||
this.priority = priority;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.alibaba.qlexpress4.operator;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Blacklist strategy that forbids specified operators
|
||||
* If blackOperators is empty, all operators are allowed
|
||||
*
|
||||
* @author QLExpress Team
|
||||
*/
|
||||
public class BlackOperatorCheckStrategy implements OperatorCheckStrategy {
|
||||
|
||||
private final Set<String> blackOperators;
|
||||
|
||||
public BlackOperatorCheckStrategy(Set<String> blackOperators) {
|
||||
if (blackOperators == null) {
|
||||
this.blackOperators = Collections.emptySet();
|
||||
}
|
||||
else {
|
||||
this.blackOperators = Collections.unmodifiableSet(blackOperators);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowed(String operator) {
|
||||
return !blackOperators.contains(operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getOperators() {
|
||||
return blackOperators;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.alibaba.qlexpress4.operator;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author zhoutao
|
||||
*/
|
||||
public class DefaultOperatorCheckStrategy implements OperatorCheckStrategy {
|
||||
|
||||
private static final DefaultOperatorCheckStrategy INSTANCE = new DefaultOperatorCheckStrategy();
|
||||
|
||||
public static DefaultOperatorCheckStrategy getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowed(String operator) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getOperators() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.alibaba.qlexpress4.operator;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Operator restriction strategy interface.
|
||||
* Defines the contract for checking whether an operator is allowed.
|
||||
*
|
||||
* Supported operators (organized by category):
|
||||
* - arithmetic: {@code +, -, *, /, %, mod, +=, -=, *=, /=, %=}
|
||||
* - assign: {@code =}
|
||||
* - bit: {@code &, |, ^, ~, <<, >>, >>>, &=, |=, ^=, <<=, >>=, >>>=}
|
||||
* - collection: {@code in, not_in}
|
||||
* - compare: {@code ==, !=, <>, <, <=, >, >=}
|
||||
* - logic: {@code &&, ||, !, and, or}
|
||||
* - string: {@code like, not_like}
|
||||
* - unary: {@code ++, --, +, - (unary)}
|
||||
* - root: {@code instanceof}
|
||||
*
|
||||
* Usage example:
|
||||
* <pre>{@code
|
||||
* // Whitelist strategy - only allow + and *
|
||||
* Set<String> allowed = new HashSet<>(Arrays.asList("+", "*"));
|
||||
* OperatorCheckStrategy strategy = OperatorCheckStrategy.whitelist(allowed);
|
||||
*
|
||||
* // Blacklist strategy - forbid assignment operator
|
||||
* Set<String> forbidden = new HashSet<>(Arrays.asList("="));
|
||||
* OperatorCheckStrategy strategy = OperatorCheckStrategy.blacklist(forbidden);
|
||||
*
|
||||
* // Allow all operators
|
||||
* OperatorCheckStrategy strategy = OperatorCheckStrategy.allowAll();
|
||||
* }</pre>
|
||||
*/
|
||||
public interface OperatorCheckStrategy {
|
||||
|
||||
static OperatorCheckStrategy allowAll() {
|
||||
return DefaultOperatorCheckStrategy.getInstance();
|
||||
}
|
||||
|
||||
static OperatorCheckStrategy whitelist(Set<String> allowedOperators) {
|
||||
return new WhiteOperatorCheckStrategy(allowedOperators);
|
||||
}
|
||||
|
||||
static OperatorCheckStrategy blacklist(Set<String> forbiddenOperators) {
|
||||
return new BlackOperatorCheckStrategy(forbiddenOperators);
|
||||
}
|
||||
|
||||
boolean isAllowed(String operator);
|
||||
|
||||
Set<String> getOperators();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.alibaba.qlexpress4.operator;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Whitelist strategy that only allows specified operators
|
||||
* If allowedOperators is empty, no operators are allowed
|
||||
*
|
||||
* @author QLExpress Team
|
||||
*/
|
||||
public class WhiteOperatorCheckStrategy implements OperatorCheckStrategy {
|
||||
|
||||
private final Set<String> allowedOperators;
|
||||
|
||||
public WhiteOperatorCheckStrategy(Set<String> allowedOperators) {
|
||||
if (allowedOperators == null) {
|
||||
this.allowedOperators = Collections.emptySet();
|
||||
}
|
||||
else {
|
||||
this.allowedOperators = Collections.unmodifiableSet(allowedOperators);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowed(String operator) {
|
||||
return allowedOperators.contains(operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getOperators() {
|
||||
return allowedOperators;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.alibaba.qlexpress4.proxy;
|
||||
|
||||
import com.alibaba.qlexpress4.runtime.QLambda;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* Author: TaoKan
|
||||
*/
|
||||
public class QLambdaInvocationHandler implements InvocationHandler {
|
||||
private final QLambda qLambda;
|
||||
|
||||
public QLambdaInvocationHandler(QLambda qLambda) {
|
||||
this.qLambda = qLambda;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args)
|
||||
throws Throwable {
|
||||
return Modifier.isAbstract(method.getModifiers()) ? qLambda.call(args).getResult().get()
|
||||
: method.getReturnType() == String.class && "toString".equals(method.getName()) ? "QLambdaProxy"
|
||||
: method.invoke(args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.alibaba.qlexpress4.runtime;
|
||||
|
||||
import com.alibaba.qlexpress4.runtime.function.CustomFunction;
|
||||
import com.alibaba.qlexpress4.runtime.scope.QScope;
|
||||
import com.alibaba.qlexpress4.runtime.trace.QTraces;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class DelegateQContext implements QContext {
|
||||
|
||||
private final QRuntime qRuntime;
|
||||
|
||||
private QScope qScope;
|
||||
|
||||
public DelegateQContext(QRuntime qRuntime, QScope qScope) {
|
||||
this.qRuntime = qRuntime;
|
||||
this.qScope = qScope;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long scriptStartTimeStamp() {
|
||||
return qRuntime.scriptStartTimeStamp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> attachment() {
|
||||
return qRuntime.attachment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReflectLoader getReflectLoader() {
|
||||
return qRuntime.getReflectLoader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public QTraces getTraces() {
|
||||
return qRuntime.getTraces();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value getSymbol(String varName) {
|
||||
return qScope.getSymbol(varName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSymbolValue(String varName) {
|
||||
return qScope.getSymbolValue(varName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void defineLocalSymbol(String varName, Class<?> varClz, Object value) {
|
||||
qScope.defineLocalSymbol(varName, varClz, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void defineFunction(String functionName, CustomFunction function) {
|
||||
qScope.defineFunction(functionName, function);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomFunction getFunction(String functionName) {
|
||||
return qScope.getFunction(functionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, CustomFunction> getFunctionTable() {
|
||||
return qScope.getFunctionTable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void push(Value value) {
|
||||
qScope.push(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parameters pop(int number) {
|
||||
return qScope.pop(number);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value pop() {
|
||||
return qScope.pop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value peek() {
|
||||
return qScope.peek();
|
||||
}
|
||||
|
||||
@Override
|
||||
public QScope getParent() {
|
||||
return qScope.getParent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public QScope getCurrentScope() {
|
||||
return qScope;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QScope newScope() {
|
||||
return qScope = qScope.newScope();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeScope() {
|
||||
qScope = qScope.getParent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.alibaba.qlexpress4.runtime;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class ExceptionTable {
|
||||
|
||||
public static final ExceptionTable EMPTY = new ExceptionTable(Collections.emptyList(), null);
|
||||
|
||||
private final List<Map.Entry<Class<?>, Integer>> handlerPosMap;
|
||||
|
||||
/**
|
||||
* nullable
|
||||
*/
|
||||
private final Integer finalPos;
|
||||
|
||||
public ExceptionTable(List<Map.Entry<Class<?>, Integer>> handlerPosMap, Integer finalPos) {
|
||||
this.handlerPosMap = handlerPosMap;
|
||||
this.finalPos = finalPos;
|
||||
}
|
||||
|
||||
public Integer getRelativePos(Object throwObj) {
|
||||
for (Map.Entry<Class<?>, Integer> classHandlerMap : handlerPosMap) {
|
||||
if (throwObj == null) {
|
||||
return classHandlerMap.getValue();
|
||||
}
|
||||
else if (classHandlerMap.getKey().isAssignableFrom(throwObj.getClass())) {
|
||||
return classHandlerMap.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Integer getFinalPos() {
|
||||
return finalPos;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.alibaba.qlexpress4.runtime;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class FixedSizeStack {
|
||||
|
||||
private final Value[] elements;
|
||||
|
||||
/**
|
||||
* next element to push
|
||||
*/
|
||||
private int cursor = 0;
|
||||
|
||||
public FixedSizeStack(int size) {
|
||||
this.elements = new Value[size];
|
||||
}
|
||||
|
||||
public void push(Value ele) {
|
||||
elements[cursor++] = ele;
|
||||
}
|
||||
|
||||
public Value pop() {
|
||||
return elements[--cursor];
|
||||
}
|
||||
|
||||
public Value peak() {
|
||||
return elements[cursor - 1];
|
||||
}
|
||||
|
||||
public Parameters pop(int n) {
|
||||
cursor -= n;
|
||||
return new StackSwapParameters(elements, cursor, n);
|
||||
}
|
||||
|
||||
private static class StackSwapParameters implements Parameters {
|
||||
|
||||
private final Value[] elements;
|
||||
|
||||
private final int start;
|
||||
|
||||
private final int length;
|
||||
|
||||
private StackSwapParameters(Value[] elements, int start, int length) {
|
||||
this.elements = elements;
|
||||
this.start = start;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value get(int i) {
|
||||
return i >= length ? null : elements[start + i];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
24
src/main/java/com/alibaba/qlexpress4/runtime/IMethod.java
Normal file
24
src/main/java/com/alibaba/qlexpress4/runtime/IMethod.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package com.alibaba.qlexpress4.runtime;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public interface IMethod {
|
||||
|
||||
Class<?>[] getParameterTypes();
|
||||
|
||||
boolean isVarArgs();
|
||||
|
||||
boolean isAccess();
|
||||
|
||||
void setAccessible(boolean flag);
|
||||
|
||||
String getName();
|
||||
|
||||
Class<?> getDeclaringClass();
|
||||
|
||||
Object invoke(Object obj, Object[] args)
|
||||
throws InvocationTargetException, IllegalAccessException;
|
||||
}
|
||||
56
src/main/java/com/alibaba/qlexpress4/runtime/JvmIMethod.java
Normal file
56
src/main/java/com/alibaba/qlexpress4/runtime/JvmIMethod.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package com.alibaba.qlexpress4.runtime;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class JvmIMethod implements IMethod {
|
||||
private final Method method;
|
||||
|
||||
public JvmIMethod(Method method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public Method getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?>[] getParameterTypes() {
|
||||
return method.getParameterTypes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVarArgs() {
|
||||
return method.isVarArgs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccess() {
|
||||
return Modifier.isPublic(method.getDeclaringClass().getModifiers()) && Modifier.isPublic(method.getModifiers());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessible(boolean flag) {
|
||||
method.setAccessible(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return method.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getDeclaringClass() {
|
||||
return method.getDeclaringClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object obj, Object[] args)
|
||||
throws InvocationTargetException, IllegalAccessException {
|
||||
return method.invoke(obj, args);
|
||||
}
|
||||
}
|
||||
40
src/main/java/com/alibaba/qlexpress4/runtime/LeftValue.java
Normal file
40
src/main/java/com/alibaba/qlexpress4/runtime/LeftValue.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package com.alibaba.qlexpress4.runtime;
|
||||
|
||||
import com.alibaba.qlexpress4.exception.ErrorReporter;
|
||||
import com.alibaba.qlexpress4.exception.QLErrorCodes;
|
||||
import com.alibaba.qlexpress4.runtime.data.convert.ObjTypeConvertor;
|
||||
|
||||
/**
|
||||
* assignable value
|
||||
* <p>
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public interface LeftValue extends Value {
|
||||
|
||||
Class<?> getDefinedType();
|
||||
|
||||
@Override
|
||||
default Class<?> getType() {
|
||||
Class<?> definedType = getDefinedType();
|
||||
return definedType == null ? Value.super.getType() : definedType;
|
||||
}
|
||||
|
||||
default void set(Object newValue, ErrorReporter errorReporter) {
|
||||
Class<?> defineType = getDefinedType();
|
||||
ObjTypeConvertor.QConverted result = ObjTypeConvertor.cast(newValue, defineType);
|
||||
if (!result.isConvertible()) {
|
||||
throw errorReporter.reportFormat(QLErrorCodes.INCOMPATIBLE_ASSIGNMENT_TYPE.name(),
|
||||
QLErrorCodes.INCOMPATIBLE_ASSIGNMENT_TYPE.getErrorMsg(),
|
||||
newValue == null ? "null" : newValue.getClass().getName(),
|
||||
defineType.getName());
|
||||
}
|
||||
setInner(result.getConverted());
|
||||
}
|
||||
|
||||
void setInner(Object newValue);
|
||||
|
||||
/**
|
||||
* @return Nullable
|
||||
*/
|
||||
String getSymbolName();
|
||||
}
|
||||
242
src/main/java/com/alibaba/qlexpress4/runtime/MemberResolver.java
Normal file
242
src/main/java/com/alibaba/qlexpress4/runtime/MemberResolver.java
Normal file
@@ -0,0 +1,242 @@
|
||||
package com.alibaba.qlexpress4.runtime;
|
||||
|
||||
import com.alibaba.qlexpress4.annotation.QLAlias;
|
||||
import com.alibaba.qlexpress4.utils.BasicUtil;
|
||||
import com.alibaba.qlexpress4.utils.CacheUtil;
|
||||
import com.alibaba.qlexpress4.utils.QLAliasUtils;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class MemberResolver {
|
||||
|
||||
public enum MatchPriority {
|
||||
MISMATCH(-1),
|
||||
// e.g. Integer -> Number
|
||||
EXTEND(0),
|
||||
// e.g. BigDecimal -> int
|
||||
NUMBER_DEMOTION(9),
|
||||
// e.g. int -> long
|
||||
// 1 -> 8
|
||||
NUMBER_PROMOTION(8),
|
||||
// e.g. Integer -> int
|
||||
UNBOX(9),
|
||||
// e.g. QLambda -> Function, Runnable, ...
|
||||
LAMBDA(10),
|
||||
// e.g. Integer -> Integer
|
||||
EQUAL(11);
|
||||
|
||||
public final int priority;
|
||||
|
||||
MatchPriority(int priority) {
|
||||
this.priority = priority;
|
||||
}
|
||||
}
|
||||
|
||||
public static Constructor<?> resolveConstructor(Class<?> cls, Class<?>[] argTypes) {
|
||||
Constructor<?>[] constructors = cls.getConstructors();
|
||||
|
||||
// simple match
|
||||
Class<?>[][] candidates = new Class<?>[constructors.length][];
|
||||
for (int i = 0; i < constructors.length; i++) {
|
||||
candidates[i] = constructors[i].getParameterTypes();
|
||||
}
|
||||
Integer bestIndex = resolveBestMatch(candidates, argTypes);
|
||||
if (bestIndex != null) {
|
||||
return constructors[bestIndex];
|
||||
}
|
||||
|
||||
// var args match
|
||||
List<Class<?>[]> varArgsCandidates = new ArrayList<>(constructors.length);
|
||||
List<Integer> varArgsConstructorI = new ArrayList<>(constructors.length);
|
||||
for (int i = 0; i < constructors.length; i++) {
|
||||
Constructor<?> constructor = constructors[i];
|
||||
if (!constructor.isVarArgs()) {
|
||||
continue;
|
||||
}
|
||||
varArgsCandidates.add(adapt2VarArgTypes(constructor.getParameterTypes(), argTypes.length));
|
||||
varArgsConstructorI.add(i);
|
||||
}
|
||||
Integer varArgBestIndex = resolveBestMatch(varArgsCandidates.toArray(new Class[0][]), argTypes);
|
||||
if (varArgBestIndex == null) {
|
||||
return null;
|
||||
}
|
||||
return constructors[varArgsConstructorI.get(varArgBestIndex)];
|
||||
}
|
||||
|
||||
public static IMethod resolveMethod(Class<?> cls, String methodName, Class<?>[] argTypes, boolean isStatic,
|
||||
boolean allowPrivate) {
|
||||
Class<?> curCls = cls;
|
||||
List<Class<?>> inters = new ArrayList<>();
|
||||
while (curCls != null) {
|
||||
IMethod method = resolveDeclaredMethod(curCls, methodName, argTypes, isStatic, allowPrivate);
|
||||
if (method != null) {
|
||||
return method;
|
||||
}
|
||||
// collect interfaces implemented by current class to search default methods later
|
||||
Class<?>[] curInters = curCls.getInterfaces();
|
||||
inters.addAll(Arrays.asList(curInters));
|
||||
|
||||
curCls = curCls.getSuperclass();
|
||||
}
|
||||
// interface method
|
||||
return resolveIntersMethod(inters.toArray(new Class[0]), methodName, argTypes, isStatic);
|
||||
}
|
||||
|
||||
private static IMethod resolveIntersMethod(Class<?>[] inters, String methodName, Class<?>[] argTypes,
|
||||
boolean isStatic) {
|
||||
for (Class<?> inter : inters) {
|
||||
IMethod method = resolveInterMethod(inter, methodName, argTypes, isStatic);
|
||||
if (method != null) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static IMethod resolveInterMethod(Class<?> inter, String methodName, Class<?>[] argTypes,
|
||||
boolean isStatic) {
|
||||
// no private method in interface, so pass false to 'allowPrivate'
|
||||
IMethod method = resolveDeclaredMethod(inter, methodName, argTypes, isStatic, false);
|
||||
if (method != null) {
|
||||
return method;
|
||||
}
|
||||
return resolveIntersMethod(inter.getInterfaces(), methodName, argTypes, isStatic);
|
||||
}
|
||||
|
||||
public static int resolvePriority(Class<?>[] paramTypes, Class<?>[] argTypes) {
|
||||
if (paramTypes.length != argTypes.length) {
|
||||
return MatchPriority.MISMATCH.priority;
|
||||
}
|
||||
int methodPriority = MatchPriority.EQUAL.priority;
|
||||
for (int i = 0; i < paramTypes.length; i++) {
|
||||
Class<?> paramType = paramTypes[i];
|
||||
Class<?> argType = argTypes[i];
|
||||
int paramPriority = resolveArgPriority(paramType, argType);
|
||||
if (paramPriority == MatchPriority.MISMATCH.priority) {
|
||||
return paramPriority;
|
||||
}
|
||||
if (paramPriority < methodPriority) {
|
||||
// methodPriority is min(paramPriority)
|
||||
methodPriority = paramPriority;
|
||||
}
|
||||
}
|
||||
return methodPriority;
|
||||
}
|
||||
|
||||
private static IMethod resolveDeclaredMethod(Class<?> cls, String methodName, Class<?>[] argTypes, boolean isStatic,
|
||||
boolean allowPrivate) {
|
||||
return resolveMethod(getDeclaredMethod(cls, methodName, isStatic, allowPrivate), argTypes);
|
||||
}
|
||||
|
||||
public static IMethod resolveMethod(List<? extends IMethod> methods, Class<?>[] argTypes) {
|
||||
// simple match
|
||||
Class<?>[][] candidates = new Class<?>[methods.size()][];
|
||||
for (int i = 0; i < methods.size(); i++) {
|
||||
IMethod declaredMethod = methods.get(i);
|
||||
candidates[i] = declaredMethod.getParameterTypes();
|
||||
}
|
||||
Integer bestIndex = resolveBestMatch(candidates, argTypes);
|
||||
if (bestIndex != null) {
|
||||
return methods.get(bestIndex);
|
||||
}
|
||||
|
||||
// var args match
|
||||
List<Class<?>[]> varArgsCandidates = new ArrayList<>(methods.size());
|
||||
List<Integer> varArgsMethodI = new ArrayList<>(methods.size());
|
||||
for (int i = 0; i < methods.size(); i++) {
|
||||
IMethod declaredMethod = methods.get(i);
|
||||
if (!declaredMethod.isVarArgs()) {
|
||||
continue;
|
||||
}
|
||||
varArgsCandidates.add(adapt2VarArgTypes(declaredMethod.getParameterTypes(), argTypes.length));
|
||||
varArgsMethodI.add(i);
|
||||
}
|
||||
Integer varArgBestIndex = resolveBestMatch(varArgsCandidates.toArray(new Class[0][]), argTypes);
|
||||
if (varArgBestIndex == null) {
|
||||
return null;
|
||||
}
|
||||
return methods.get(varArgsMethodI.get(varArgBestIndex));
|
||||
}
|
||||
|
||||
private static List<IMethod> getDeclaredMethod(Class<?> cls, String methodName, boolean isStatic,
|
||||
boolean allowPrivate) {
|
||||
Method[] declaredMethods = cls.getDeclaredMethods();
|
||||
List<IMethod> result = new ArrayList<>(declaredMethods.length);
|
||||
for (Method declaredMethod : declaredMethods) {
|
||||
if (!methodName.equals(declaredMethod.getName())
|
||||
&& !QLAliasUtils.matchQLAlias(methodName, declaredMethod.getAnnotationsByType(QLAlias.class))) {
|
||||
continue;
|
||||
}
|
||||
if ((!isStatic || BasicUtil.isStatic(declaredMethod))
|
||||
&& (allowPrivate || BasicUtil.isPublic(declaredMethod))) {
|
||||
result.add(new JvmIMethod(declaredMethod));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Class<?>[] adapt2VarArgTypes(Class<?>[] parameterTypes, int argLength) {
|
||||
Class<?> varItemType = parameterTypes[parameterTypes.length - 1].getComponentType();
|
||||
|
||||
Class<?>[] varParamTypes = new Class<?>[argLength];
|
||||
System.arraycopy(parameterTypes, 0, varParamTypes, 0, parameterTypes.length - 1);
|
||||
for (int i = parameterTypes.length - 1; i < argLength; i++) {
|
||||
varParamTypes[i] = varItemType;
|
||||
}
|
||||
return varParamTypes;
|
||||
}
|
||||
|
||||
public static Integer resolveBestMatch(Class<?>[][] candidates, Class<?>[] argTypes) {
|
||||
Integer bestMatchIndex = null;
|
||||
int bestPriority = MatchPriority.MISMATCH.priority;
|
||||
for (int i = 0; i < candidates.length; i++) {
|
||||
Class<?>[] candidate = candidates[i];
|
||||
int priority = resolvePriority(candidate, argTypes);
|
||||
if (priority > bestPriority) {
|
||||
bestPriority = priority;
|
||||
bestMatchIndex = i;
|
||||
}
|
||||
}
|
||||
return bestMatchIndex;
|
||||
}
|
||||
|
||||
private static int resolveArgPriority(Class<?> paramType, Class<?> argType) {
|
||||
if (paramType == argType) {
|
||||
return MatchPriority.EQUAL.priority;
|
||||
}
|
||||
if (CacheUtil.isFunctionInterface(paramType) && QLambda.class.isAssignableFrom(argType)) {
|
||||
return MatchPriority.LAMBDA.priority;
|
||||
}
|
||||
|
||||
Class<?> primitiveArgCls = argType.isPrimitive() ? argType : BasicUtil.transToPrimitive(argType);
|
||||
Class<?> primitiveParamCls = paramType.isPrimitive() ? paramType : BasicUtil.transToPrimitive(paramType);
|
||||
if (primitiveArgCls != null && primitiveArgCls == primitiveParamCls) {
|
||||
return MatchPriority.UNBOX.priority;
|
||||
}
|
||||
|
||||
Integer paramNumLevel = BasicUtil.numberPromoteLevel(paramType);
|
||||
Integer argNumLevel = BasicUtil.numberPromoteLevel(argType);
|
||||
if (paramNumLevel != null && argNumLevel != null) {
|
||||
return paramNumLevel >= argNumLevel ? MatchPriority.NUMBER_PROMOTION.priority + argNumLevel - paramNumLevel
|
||||
: MatchPriority.NUMBER_DEMOTION.priority;
|
||||
}
|
||||
|
||||
// Handle primitive to Object boxing conversion
|
||||
if (argType.isPrimitive() && paramType == Object.class) {
|
||||
return MatchPriority.EXTEND.priority;
|
||||
}
|
||||
|
||||
if (argType == Nothing.class || paramType.isAssignableFrom(argType)) {
|
||||
return MatchPriority.EXTEND.priority;
|
||||
}
|
||||
return MatchPriority.MISMATCH.priority;
|
||||
}
|
||||
}
|
||||
34
src/main/java/com/alibaba/qlexpress4/runtime/MetaClass.java
Normal file
34
src/main/java/com/alibaba/qlexpress4/runtime/MetaClass.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package com.alibaba.qlexpress4.runtime;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class MetaClass {
|
||||
|
||||
private final Class<?> clz;
|
||||
|
||||
public MetaClass(Class<?> clz) {
|
||||
this.clz = clz;
|
||||
}
|
||||
|
||||
public Class<?> getClz() {
|
||||
return clz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
MetaClass metaClass = (MetaClass)o;
|
||||
return clz.equals(metaClass.clz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(clz);
|
||||
}
|
||||
}
|
||||
12
src/main/java/com/alibaba/qlexpress4/runtime/Nothing.java
Normal file
12
src/main/java/com/alibaba/qlexpress4/runtime/Nothing.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.alibaba.qlexpress4.runtime;
|
||||
|
||||
/**
|
||||
* subclass of any object
|
||||
*/
|
||||
public class Nothing {
|
||||
|
||||
private Nothing() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
26
src/main/java/com/alibaba/qlexpress4/runtime/Parameters.java
Normal file
26
src/main/java/com/alibaba/qlexpress4/runtime/Parameters.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.alibaba.qlexpress4.runtime;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public interface Parameters {
|
||||
|
||||
default Object getValue(int i) {
|
||||
Value boxedValue = get(i);
|
||||
return boxedValue == null ? null : boxedValue.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* get parameters in i position
|
||||
*
|
||||
* @param i index
|
||||
* @return value in index, null if exceed parameters' length
|
||||
*/
|
||||
Value get(int i);
|
||||
|
||||
/**
|
||||
* parameters size
|
||||
* @return size
|
||||
*/
|
||||
int size();
|
||||
}
|
||||
13
src/main/java/com/alibaba/qlexpress4/runtime/QContext.java
Normal file
13
src/main/java/com/alibaba/qlexpress4/runtime/QContext.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.alibaba.qlexpress4.runtime;
|
||||
|
||||
import com.alibaba.qlexpress4.runtime.scope.QScope;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public interface QContext extends QScope, QRuntime {
|
||||
|
||||
QScope getCurrentScope();
|
||||
|
||||
void closeScope();
|
||||
}
|
||||
84
src/main/java/com/alibaba/qlexpress4/runtime/QLambda.java
Normal file
84
src/main/java/com/alibaba/qlexpress4/runtime/QLambda.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package com.alibaba.qlexpress4.runtime;
|
||||
|
||||
import com.alibaba.qlexpress4.runtime.function.CustomFunction;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface QLambda
|
||||
extends Runnable, Supplier<Object>, Consumer<Object>, Predicate<Object>, Function<Object, Object> {
|
||||
|
||||
/**
|
||||
* @param params params of lambda
|
||||
* @return result of lambda
|
||||
* @throws Throwable {@link com.alibaba.qlexpress4.exception.UserDefineException} for custom error message
|
||||
*/
|
||||
QResult call(Object... params)
|
||||
throws Throwable;
|
||||
|
||||
/**
|
||||
* Get functions defined inside this lambda when invoked with parameters.
|
||||
*
|
||||
* @param params parameters passed to the lambda
|
||||
* @return function table defined by the lambda (empty by default)
|
||||
* @throws Throwable if user code throws an exception while collecting functions
|
||||
*/
|
||||
default Map<String, CustomFunction> getFunctionDefined(Object... params)
|
||||
throws Throwable {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
default Object get() {
|
||||
try {
|
||||
return call().getResult().get();
|
||||
}
|
||||
catch (Throwable t) {
|
||||
throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
default void accept(Object o) {
|
||||
try {
|
||||
call(o);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
default void run() {
|
||||
try {
|
||||
call();
|
||||
}
|
||||
catch (Throwable t) {
|
||||
throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean test(Object o) {
|
||||
try {
|
||||
return (boolean)call(o).getResult().get();
|
||||
}
|
||||
catch (Throwable t) {
|
||||
throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
default Object apply(Object o) {
|
||||
try {
|
||||
return call(o).getResult().get();
|
||||
}
|
||||
catch (Throwable t) {
|
||||
throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.alibaba.qlexpress4.runtime;
|
||||
|
||||
import com.alibaba.qlexpress4.QLOptions;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public interface QLambdaDefinition {
|
||||
|
||||
QLambda toLambda(QContext qContext, QLOptions qlOptions, boolean newEnv);
|
||||
|
||||
void println(int depth, Consumer<String> debug);
|
||||
|
||||
String getName();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.alibaba.qlexpress4.runtime;
|
||||
|
||||
import com.alibaba.qlexpress4.QLOptions;
|
||||
import com.alibaba.qlexpress4.utils.PrintlnUtils;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class QLambdaDefinitionEmpty implements QLambdaDefinition {
|
||||
|
||||
public static QLambdaDefinition INSTANCE = new QLambdaDefinitionEmpty();
|
||||
|
||||
@Override
|
||||
public QLambda toLambda(QContext qContext, QLOptions qlOptions, boolean newEnv) {
|
||||
return QLambdaEmpty.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(int depth, Consumer<String> debug) {
|
||||
PrintlnUtils.printlnByCurDepth(depth, getName(), debug);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "EmptyLambdaDefinition";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.alibaba.qlexpress4.runtime;
|
||||
|
||||
import com.alibaba.qlexpress4.QLOptions;
|
||||
import com.alibaba.qlexpress4.runtime.instruction.CloseScopeInstruction;
|
||||
import com.alibaba.qlexpress4.runtime.instruction.NewScopeInstruction;
|
||||
import com.alibaba.qlexpress4.runtime.instruction.QLInstruction;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
public class QLambdaDefinitionInner implements QLambdaDefinition {
|
||||
|
||||
/**
|
||||
* function name
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
private final QLInstruction[] instructions;
|
||||
|
||||
private final List<Param> paramsType;
|
||||
|
||||
private final int maxStackSize;
|
||||
|
||||
public QLambdaDefinitionInner(String name, List<QLInstruction> instructions, List<Param> paramsType,
|
||||
int maxStackSize) {
|
||||
this.name = name;
|
||||
this.instructions = instructions.toArray(new QLInstruction[0]);
|
||||
this.paramsType = paramsType;
|
||||
this.maxStackSize = maxStackSize;
|
||||
}
|
||||
|
||||
public QLambdaDefinitionInner(String name, QLInstruction[] instructions, List<Param> paramsType, int maxStackSize) {
|
||||
this.name = name;
|
||||
this.instructions = instructions;
|
||||
this.paramsType = paramsType;
|
||||
this.maxStackSize = maxStackSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public QLInstruction[] getInstructions() {
|
||||
return instructions;
|
||||
}
|
||||
|
||||
public List<Param> getParamsType() {
|
||||
return paramsType;
|
||||
}
|
||||
|
||||
public int getMaxStackSize() {
|
||||
return maxStackSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QLambda toLambda(QContext qContext, QLOptions qlOptions, boolean newEnv) {
|
||||
return new QLambdaInner(this, new DelegateQContext(qContext, qContext.getCurrentScope()), qlOptions, newEnv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(int depth, Consumer<String> debug) {
|
||||
for (int i = 0; i < instructions.length; i++) {
|
||||
instructions[i].println(i, depth, debug);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Param {
|
||||
private final String name;
|
||||
|
||||
private final Class<?> clazz;
|
||||
|
||||
public Param(String name, Class<?> clazz) {
|
||||
this.name = name;
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Class<?> getClazz() {
|
||||
return clazz;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user