+29
.github/workflows/maven.yml
+29
.github/workflows/maven.yml
···
1
+
# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
2
+
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
3
+
4
+
name: Java CI with Maven
5
+
6
+
on:
7
+
push:
8
+
branches: [ "main" ]
9
+
pull_request:
10
+
branches: [ "main" ]
11
+
12
+
jobs:
13
+
build:
14
+
15
+
runs-on: ubuntu-latest
16
+
17
+
steps:
18
+
- uses: actions/checkout@v3
19
+
- name: Set up JDK 11
20
+
uses: actions/setup-java@v3
21
+
with:
22
+
java-version: '11'
23
+
distribution: 'temurin'
24
+
cache: maven
25
+
- name: Verify and analyze with SonarCloud
26
+
run: mvn -B verify sonar:sonar -Dsonar.projectKey=ezylang_EvalEx -Dsonar.organization=ezylang -Dsonar.host.url=https://sonarcloud.io -Dsonar.coverage.jacoco.xmlReportPaths=/home/runner/work/EvalEx/EvalEx/target/site/jacoco/jacoco.xml
27
+
env:
28
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29
+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+28
-4
.gitignore
+28
-4
.gitignore
···
1
-
/bin
2
-
/.settings
1
+
# Eclipse
3
2
.classpath
4
3
.project
4
+
.settings/
5
+
6
+
# Intellij
7
+
.idea/
5
8
*.iml
6
-
.idea
7
-
target
9
+
*.iws
10
+
11
+
# Mac
12
+
.DS_Store
13
+
14
+
# Maven
15
+
log/
16
+
target/
17
+
18
+
# Compiled class file
19
+
*.class
20
+
21
+
# Log file
22
+
*.log
23
+
24
+
# Package Files #
25
+
*.jar
26
+
*.war
27
+
*.nar
28
+
*.ear
29
+
30
+
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
31
+
hs_err_pid*
-34
.travis.yml
-34
.travis.yml
···
1
-
language: java
2
-
sudo: false
3
-
install: true
4
-
jdk: openjdk11
5
-
addons:
6
-
sonarcloud:
7
-
organization: uklimaschewski
8
-
token: "$SONAR_TOKEN"
9
-
script:
10
-
- mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install sonar:sonar -Dsonar.host.url=https://sonarcloud.io
11
-
-Dsonar.projectKey=uklimaschewski_EvalEx -Pcoverage
12
-
cache:
13
-
directories:
14
-
- "$HOME/.m2/repository"
15
-
before_deploy:
16
-
- openssl aes-256-cbc -K $encrypted_044baa266d1a_key -iv $encrypted_044baa266d1a_iv
17
-
-in .travis/gpg.asc.enc -out .travis/gpg.asc -d
18
-
deploy:
19
-
- skip_cleanup: true
20
-
provider: script
21
-
script: bash ./deploy.sh
22
-
on:
23
-
branch: master
24
-
- skip_cleanup: true
25
-
provider: script
26
-
script: bash ./deploy.sh
27
-
on:
28
-
tags: true
29
-
env:
30
-
global:
31
-
- secure: jGr5mZsN7sYHwDfcHQ9BpSUkTRRTe9abIlMzz8xrBmO/Us+MioIKxRIp8CBiLW9/NGfAsuIt/74wirUzJbHkyVyH/rLV2BHB6vaTe+9IJpl36sPT//0DdAQH5I1iDlbTqkOMGkkjkVn5LwD98dA7AkrHr128lSmUplof1DXHiM0hdqRMseisHpyjO7g6wnkFX/gLQtkuMDIzSC0+ZGrHiVkbP97msgyiWCz2U1wf77D/TS2Ohgeis+7ygZc0OPM8qkyzylIgCQVfnOAoY/95RIXm3MH5HjfqJQSbz6cKX5IK+hiL8n5p3228MsIwOMlA4OLxFpHtW9dHi7h8ei0K+W/8a+V8XK1P/TP2RgaER3mWQ+lmEZ9Qyvbf3E7VVyawubE+l297357ty+FggEwc4gXS+1vRvb9dRLmkVuVov4lQWV2ZVqhhTXwUjL3tWPdlv78bxv453r6eX+shPZQtWMkcRjYgEFuntDcfBgIgagBpO1QBUiiIKYhsHx/KWp/PFR34a/QRXDBWcXtYBUzQG5i8S15c5+Ql40pgQFtw7RWUErENDndfccHEras5fx6VzkVYzaQDdRk4caeLzTTK/52cz/JCfVAT1bQ0rUGQoQ+vllUzbr6kUzjthur3RtbO/QhhemQsv7Jf+rFyzuhueoBCFylyH4m+DOIpTIv9VxU=
32
-
- secure: lgMcJkV+MFMryleJKt/5t96Ifane1JQ+DMnSbKm4uFVnjdOFuQm3DeL5FidBB+e2NsOpO1+ZQgWnYcNAphv6Gc1ToRBSXRVkQrIE9HXFDULKl1qdGaHFVgdW4Ypa9u8vcSHSRXeLtPz/uJn8Rm7H7QaFs2thaNusUgWaO3V8rbeoKDNE4vyDKptJmVw/9n2noEOrQPn4ctl/6wIoAW01E9pojhRyYeNqv1hIfMseG2sdLjJEXALa8jFKG4fgjXSUCaEp1wW7oLYhOyPkOeqjRYitAe5iOh22njh2vtacXyIiNvSI8q5PC5xZbq3MjxkSerxlnhx5jdCRdHlH7udZC/hQ9ouxkisZFjQyi+FRj4zGUPb2ohUCDyXiCL2+80Y6MESZk7GXiH3j9zbJ/hLTGd2fCsKMZHeLFhQXGEV5ppTPjVYSZ4MW615b0lRdZxb9j3rvwwrc7l79KRz27Xm426HqPwhMSy6f/hx828mnCcsN8p4Yq+cXR0fsXcLCt1tp0vPBYR8jA9osPlo9Uno2PN+ax1LXvCC9srm5EfOPjPs0DfWlLU0oNm8+MUx1MgDi2vMFG7LUOwDY/vwVSfG9hyDQ3Wddm+uD0CmwJu89ndPnAvMhtKCWaNRAAsVOgumExlTM/ksfftFIStKm32q8DpK9DGLAK+/78yXGciJb8KQ=
33
-
- secure: JUXh2wRIb2Q6R7ewYDs3wWr2io+bn71sfs98csYP4Nj7I5wtDFFPivsAE1BnTHOhSXWQi3S7+Nwx9104y+RM5z2eat5gDvr7NngLD3/MDWfxeuzuzh956myOjn3rbrDV5MAGcFL6P8ELyv9LmLTiGmg5kT8Ae7VpuMXQ/zkvkbMv4kR09gCnWq7ZsG1SphaI14Sug5GGQdP3zyeyOYVOQrZGCTuDnEs7eHphz+nqxtWISS5bG/fPZys2uitsiCv0qRF0JvxP3gJYmTVlUVxs9cWzHm8p/Lr1d9YtYN291dg85yUVv9NvPveEmbVlauPpQwUGQbIodLTXErhUwHBdwObFRJMikNyLXWZ7P4Nbfc6Mb3QqMl9g2aWapJNUnp8VjjOxuJ21HBrnN2nEKozjPv01U2ugSfz/U4V+cp3lxBFquMzbg7FW3pMIaxCsUeeE75nYUNYn+tM3rajtG8M05yiFBRFBY3GmsXKGe/tNaQzC4d4it3iTP3VP+8Fmz1VsU4YBtRX3TwrQZeGUt1+LyRX3qtWXXla692juo59yylPAMGr1iE+y4NWi2lfcLRqOET2ZpYYtHJ693wHhY2HUCaLilSYMTsznQIAUMq7bU6/91oisz4+1FRfC0EMByMbWEYq/XFEbD8zbr5XR6xxdCCPOxiAPhavgsbFrLUUaYms=
34
-
- secure: Eo8k7rucRejZHS/LHrjCDzOFUUkHYZOMQki6/9BV6LVPAVBTm30+ScS5T/tfkZ85eIjqEaC8GdNuekTfkbrHwqt6L8qcUW3K3HaDUUKG+ROVhsoL5L/mp42BUkdw5WoL6FEL4ziJxHtOWigMg5y984sGz06eID0NG5R2iUHYUcZRvwJ1E0DJtqqwWy3p69uhrtDkidkXw73VDT/Ya9ktLpSecwS7s9Y1eMFZ5eTc+nsAyHh0UipFXHDkm/tqw2MxWdDQGcgohKN/4EZcoe9G5DM1y9G6q9YWEjX8aQiFkC4vKuz42tC/3l0WLB3xW3O3L9xyvMV5CSGhLFC0brgJtRfZpum+hgQwF6VBBMx8MYXfdU2FsmWb61wLT5UfBSb0SupJ5Ysm11DR+oA2fI4LRmryvzHIxWHUaIxkrYdh9593HLwEuPVIP15sxfemXnwejdzId8ir8apLTDvgAXanWsQsyfDbBkBR6o9yCVUeiLJlHA4oO305KViQyo3H1mNo3bCGT37As+6Jn9Ewz7MbDv+sPmQXBEZL3f9BNh2VinDYuNXWO7QdBHHWxVr+pD/3PeP2zyt2uL8+QF3Ql4G9Qwzz1KgB7lLnMW4RgOVoR8YU9rxQdgsqr9z6xcIlLiblqtUzFeIsuF/n0Cxpo0qDbdMMSoF/63AP6ZZBdvUSXRs=
.travis/gpg.asc.enc
.travis/gpg.asc.enc
This is a binary file and will not be displayed.
-26
.travis/mvn-settings.xml
-26
.travis/mvn-settings.xml
···
1
-
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
2
-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3
-
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
4
-
https://maven.apache.org/xsd/settings-1.0.0.xsd">
5
-
<servers>
6
-
<server>
7
-
<id>ossrh</id>
8
-
<username>${env.OSSRH_USERNAME}</username>
9
-
<password>${env.OSSRH_PASSWORD}</password>
10
-
</server>
11
-
</servers>
12
-
13
-
<profiles>
14
-
<profile>
15
-
<id>ossrh</id>
16
-
<activation>
17
-
<activeByDefault>true</activeByDefault>
18
-
</activation>
19
-
<properties>
20
-
<gpg.executable>gpg</gpg.executable>
21
-
<gpg.keyname>${env.GPG_KEY_NAME}</gpg.keyname>
22
-
<gpg.passphrase>${env.GPG_PASSPHRASE}</gpg.passphrase>
23
-
</properties>
24
-
</profile>
25
-
</profiles>
26
-
</settings>
-53
FAQ.md
-53
FAQ.md
···
1
-
EvalEx - Frequently Asked Questions (and answers)
2
-
==========
3
-
Please feel free to add content to this list.
4
-
5
-
### Can I use hexadecimal values?
6
-
7
-
Yes e.g., `BigDecimal result = new Expression("0xcafe + 0xbabe").eval();` will be correctly
8
-
evaluated by EvalEx.
9
-
10
-
### Incorrect results with large numbers
11
-
12
-
The default math context used in expressions has a precision of 7, which is too small for large
13
-
numbers. Either create the expression with a different math context, or specify the precision before
14
-
evaluating:
15
-
16
-
````Java
17
-
Expression expression=new Expression("timestamp >= 1500551315569",new MathContext(13));
18
-
````
19
-
20
-
or
21
-
22
-
````Java
23
-
expression.with("timestamp","1500551315568").setPrecision(13).eval();
24
-
````
25
-
26
-
### Results are in scientific notation (e.g. 4E+1)
27
-
28
-
When evaluation an expression, e.g.:
29
-
30
-
````Java
31
-
String result=new Expression("5*8").eval();
32
-
````
33
-
34
-
The string value in result will read `4E+1`. The reason is, that Expression.eval() returns
35
-
a `java.math.BigDecimal`
36
-
and the `toString()` method of `BigDecimal` uses scientific notation.
37
-
Use `Expression.eval().toPlainString()` instead.
38
-
39
-
### -2^2 returns 4 instead of -4
40
-
41
-
There is a bit confusion among different calculators on how the precedence and association is
42
-
handled in combination with the power of operator. Excel, for example, evaluates above expression to
43
-
4, but Google calculator -4. The default precedence for the power of operator in EvalEx is lower
44
-
than the unary minus operator and therefore, -2 is calculated first, which results in `(-2)*(-2)`,
45
-
which calculates to 4. The precedence of the power operator can be adjusted when an expression is
46
-
created:
47
-
48
-
````Java
49
-
ExpressionSettings settings=ExpressionSettings.builder()
50
-
.powerOperatorPrecedenceHigher()
51
-
.build();
52
-
BigDecimal result=new Expression("-2^2",settings).eval();
53
-
````
+201
LICENSE
+201
LICENSE
···
1
+
Apache License
2
+
Version 2.0, January 2004
3
+
http://www.apache.org/licenses/
4
+
5
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+
1. Definitions.
8
+
9
+
"License" shall mean the terms and conditions for use, reproduction,
10
+
and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+
"Licensor" shall mean the copyright owner or entity authorized by
13
+
the copyright owner that is granting the License.
14
+
15
+
"Legal Entity" shall mean the union of the acting entity and all
16
+
other entities that control, are controlled by, or are under common
17
+
control with that entity. For the purposes of this definition,
18
+
"control" means (i) the power, direct or indirect, to cause the
19
+
direction or management of such entity, whether by contract or
20
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+
outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+
"You" (or "Your") shall mean an individual or Legal Entity
24
+
exercising permissions granted by this License.
25
+
26
+
"Source" form shall mean the preferred form for making modifications,
27
+
including but not limited to software source code, documentation
28
+
source, and configuration files.
29
+
30
+
"Object" form shall mean any form resulting from mechanical
31
+
transformation or translation of a Source form, including but
32
+
not limited to compiled object code, generated documentation,
33
+
and conversions to other media types.
34
+
35
+
"Work" shall mean the work of authorship, whether in Source or
36
+
Object form, made available under the License, as indicated by a
37
+
copyright notice that is included in or attached to the work
38
+
(an example is provided in the Appendix below).
39
+
40
+
"Derivative Works" shall mean any work, whether in Source or Object
41
+
form, that is based on (or derived from) the Work and for which the
42
+
editorial revisions, annotations, elaborations, or other modifications
43
+
represent, as a whole, an original work of authorship. For the purposes
44
+
of this License, Derivative Works shall not include works that remain
45
+
separable from, or merely link (or bind by name) to the interfaces of,
46
+
the Work and Derivative Works thereof.
47
+
48
+
"Contribution" shall mean any work of authorship, including
49
+
the original version of the Work and any modifications or additions
50
+
to that Work or Derivative Works thereof, that is intentionally
51
+
submitted to Licensor for inclusion in the Work by the copyright owner
52
+
or by an individual or Legal Entity authorized to submit on behalf of
53
+
the copyright owner. For the purposes of this definition, "submitted"
54
+
means any form of electronic, verbal, or written communication sent
55
+
to the Licensor or its representatives, including but not limited to
56
+
communication on electronic mailing lists, source code control systems,
57
+
and issue tracking systems that are managed by, or on behalf of, the
58
+
Licensor for the purpose of discussing and improving the Work, but
59
+
excluding communication that is conspicuously marked or otherwise
60
+
designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+
"Contributor" shall mean Licensor and any individual or Legal Entity
63
+
on behalf of whom a Contribution has been received by Licensor and
64
+
subsequently incorporated within the Work.
65
+
66
+
2. Grant of Copyright License. Subject to the terms and conditions of
67
+
this License, each Contributor hereby grants to You a perpetual,
68
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+
copyright license to reproduce, prepare Derivative Works of,
70
+
publicly display, publicly perform, sublicense, and distribute the
71
+
Work and such Derivative Works in Source or Object form.
72
+
73
+
3. Grant of Patent License. Subject to the terms and conditions of
74
+
this License, each Contributor hereby grants to You a perpetual,
75
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+
(except as stated in this section) patent license to make, have made,
77
+
use, offer to sell, sell, import, and otherwise transfer the Work,
78
+
where such license applies only to those patent claims licensable
79
+
by such Contributor that are necessarily infringed by their
80
+
Contribution(s) alone or by combination of their Contribution(s)
81
+
with the Work to which such Contribution(s) was submitted. If You
82
+
institute patent litigation against any entity (including a
83
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+
or a Contribution incorporated within the Work constitutes direct
85
+
or contributory patent infringement, then any patent licenses
86
+
granted to You under this License for that Work shall terminate
87
+
as of the date such litigation is filed.
88
+
89
+
4. Redistribution. You may reproduce and distribute copies of the
90
+
Work or Derivative Works thereof in any medium, with or without
91
+
modifications, and in Source or Object form, provided that You
92
+
meet the following conditions:
93
+
94
+
(a) You must give any other recipients of the Work or
95
+
Derivative Works a copy of this License; and
96
+
97
+
(b) You must cause any modified files to carry prominent notices
98
+
stating that You changed the files; and
99
+
100
+
(c) You must retain, in the Source form of any Derivative Works
101
+
that You distribute, all copyright, patent, trademark, and
102
+
attribution notices from the Source form of the Work,
103
+
excluding those notices that do not pertain to any part of
104
+
the Derivative Works; and
105
+
106
+
(d) If the Work includes a "NOTICE" text file as part of its
107
+
distribution, then any Derivative Works that You distribute must
108
+
include a readable copy of the attribution notices contained
109
+
within such NOTICE file, excluding those notices that do not
110
+
pertain to any part of the Derivative Works, in at least one
111
+
of the following places: within a NOTICE text file distributed
112
+
as part of the Derivative Works; within the Source form or
113
+
documentation, if provided along with the Derivative Works; or,
114
+
within a display generated by the Derivative Works, if and
115
+
wherever such third-party notices normally appear. The contents
116
+
of the NOTICE file are for informational purposes only and
117
+
do not modify the License. You may add Your own attribution
118
+
notices within Derivative Works that You distribute, alongside
119
+
or as an addendum to the NOTICE text from the Work, provided
120
+
that such additional attribution notices cannot be construed
121
+
as modifying the License.
122
+
123
+
You may add Your own copyright statement to Your modifications and
124
+
may provide additional or different license terms and conditions
125
+
for use, reproduction, or distribution of Your modifications, or
126
+
for any such Derivative Works as a whole, provided Your use,
127
+
reproduction, and distribution of the Work otherwise complies with
128
+
the conditions stated in this License.
129
+
130
+
5. Submission of Contributions. Unless You explicitly state otherwise,
131
+
any Contribution intentionally submitted for inclusion in the Work
132
+
by You to the Licensor shall be under the terms and conditions of
133
+
this License, without any additional terms or conditions.
134
+
Notwithstanding the above, nothing herein shall supersede or modify
135
+
the terms of any separate license agreement you may have executed
136
+
with Licensor regarding such Contributions.
137
+
138
+
6. Trademarks. This License does not grant permission to use the trade
139
+
names, trademarks, service marks, or product names of the Licensor,
140
+
except as required for reasonable and customary use in describing the
141
+
origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+
7. Disclaimer of Warranty. Unless required by applicable law or
144
+
agreed to in writing, Licensor provides the Work (and each
145
+
Contributor provides its Contributions) on an "AS IS" BASIS,
146
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+
implied, including, without limitation, any warranties or conditions
148
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+
PARTICULAR PURPOSE. You are solely responsible for determining the
150
+
appropriateness of using or redistributing the Work and assume any
151
+
risks associated with Your exercise of permissions under this License.
152
+
153
+
8. Limitation of Liability. In no event and under no legal theory,
154
+
whether in tort (including negligence), contract, or otherwise,
155
+
unless required by applicable law (such as deliberate and grossly
156
+
negligent acts) or agreed to in writing, shall any Contributor be
157
+
liable to You for damages, including any direct, indirect, special,
158
+
incidental, or consequential damages of any character arising as a
159
+
result of this License or out of the use or inability to use the
160
+
Work (including but not limited to damages for loss of goodwill,
161
+
work stoppage, computer failure or malfunction, or any and all
162
+
other commercial damages or losses), even if such Contributor
163
+
has been advised of the possibility of such damages.
164
+
165
+
9. Accepting Warranty or Additional Liability. While redistributing
166
+
the Work or Derivative Works thereof, You may choose to offer,
167
+
and charge a fee for, acceptance of support, warranty, indemnity,
168
+
or other liability obligations and/or rights consistent with this
169
+
License. However, in accepting such obligations, You may act only
170
+
on Your own behalf and on Your sole responsibility, not on behalf
171
+
of any other Contributor, and only if You agree to indemnify,
172
+
defend, and hold each Contributor harmless for any liability
173
+
incurred by, or claims asserted against, such Contributor by reason
174
+
of your accepting any such warranty or additional liability.
175
+
176
+
END OF TERMS AND CONDITIONS
177
+
178
+
APPENDIX: How to apply the Apache License to your work.
179
+
180
+
To apply the Apache License to your work, attach the following
181
+
boilerplate notice, with the fields enclosed by brackets "[]"
182
+
replaced with your own identifying information. (Don't include
183
+
the brackets!) The text should be enclosed in the appropriate
184
+
comment syntax for the file format. We also recommend that a
185
+
file or class name and description of purpose be included on the
186
+
same "printed page" as the copyright notice for easier
187
+
identification within third-party archives.
188
+
189
+
Copyright [yyyy] [name of copyright owner]
190
+
191
+
Licensed under the Apache License, Version 2.0 (the "License");
192
+
you may not use this file except in compliance with the License.
193
+
You may obtain a copy of the License at
194
+
195
+
http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+
Unless required by applicable law or agreed to in writing, software
198
+
distributed under the License is distributed on an "AS IS" BASIS,
199
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+
See the License for the specific language governing permissions and
201
+
limitations under the License.
-20
LICENSE.md
-20
LICENSE.md
···
1
-
Copyright 2012-2021 Udo Klimaschewski
2
-
3
-
Permission is hereby granted, free of charge, to any person obtaining
4
-
a copy of this software and associated documentation files (the
5
-
"Software"), to deal in the Software without restriction, including
6
-
without limitation the rights to use, copy, modify, merge, publish,
7
-
distribute, sublicense, and/or sell copies of the Software, and to
8
-
permit persons to whom the Software is furnished to do so, subject to
9
-
the following conditions:
10
-
11
-
The above copyright notice and this permission notice shall be
12
-
included in all copies or substantial portions of the Software.
13
-
14
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+91
-361
README.md
+91
-361
README.md
···
1
1
EvalEx - Java Expression Evaluator
2
2
==========
3
-
[](https://travis-ci.com/uklimaschewski/EvalEx) [](https://sonarcloud.io/dashboard?id=uklimaschewski_EvalEx)
4
3
5
-
| :warning: Starting with version 3, [EvalEx will have a new repository location](https://github.com/ezylang/evalex-core)! See also the [announcement in the discussion area](https://github.com/uklimaschewski/EvalEx/discussions/291)|
6
-
| --- |
4
+

5
+
[](https://sonarcloud.io/summary/new_code?id=EvalEx)
6
+
[](https://sonarcloud.io/summary/new_code?id=EvalEx)
7
+
[](https://sonarcloud.io/summary/new_code?id=EvalEx)
8
+
[](https://sonarcloud.io/summary/new_code?id=EvalEx)
7
9
8
-
### Introduction
10
+
EvalEx is a handy expression evaluator for Java, that allows to parse and evaluate expression
11
+
strings.
9
12
10
-
EvalEx is a handy expression evaluator for Java, that allows to evaluate simple mathematical and
11
-
boolean expressions.
13
+
## Key Features:
12
14
13
-
Key Features:
14
-
15
-
- Uses BigDecimal for calculation and result
16
-
- No dependencies to external libraries
17
-
- Precision and rounding mode can be set
18
-
- Supports variables
19
-
- Standard boolean and mathematical operators
20
-
- Standard basic mathematical and boolean functions
21
-
- Custom functions and operators can be added at runtime
22
-
- Functions can be defined with a variable number of arguments (see MIN and MAX functions)
23
-
- Supports for hexadecimal numbers and scientific notations of numbers
24
-
- Supports string literals in functions
15
+
- Supports numerical, boolean, string, array and structure expressions, operations and variables.
16
+
- Array and structure support: Arrays and structures can be mixed, building arbitrary data
17
+
structures.
18
+
- Uses BigDecimal for numerical calculations.
19
+
- MathContext and number of decimal places can be configured, with optional automatic rounding.
20
+
- No dependencies to external libraries.
21
+
- Easy integration into existing systems to access data.
22
+
- Predefined boolean and mathematical operators.
23
+
- Predefined mathematical, boolean and string functions.
24
+
- Custom functions and operators can be added.
25
+
- Functions can be defined with a variable number of arguments (see MIN, MAX and SUM functions).
26
+
- Supports hexadecimal and scientific notations of numbers.
25
27
- Supports implicit multiplication, e.g. (a+b)(a-b) or 2(x-y) which equals to (a+b)\*(a-b) or 2\*(
26
28
x-y)
29
+
- Lazy evaluation of function parameters (see the IF function) and support of sub-expressions.
27
30
28
-
### Discussion
31
+
## Documentation
29
32
30
-
For announcements, questions and ideas visit the [Discussions area](https://github.com/uklimaschewski/EvalEx/discussions).
33
+
The full documentation for EvalEx can be found
34
+
on [GitHub Pages](https://ezylang.github.io/EvalEx/)
31
35
32
-
### Download / Maven
36
+
## Discussion
33
37
34
-
You can download the binaries, source code and JavaDoc jars
35
-
from [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22EvalEx%22%20g%3A%22com.udojava%22)
36
-
.
38
+
For announcements, questions and ideas visit
39
+
the [Discussions area](https://github.com/ezylang/EvalEx/discussions).
37
40
38
-
To include it in your Maven project, refer to the artifact in your pom. For example:
41
+
## Examples
39
42
40
-
````xml
43
+
### A simple example, that shows how it works in general:
41
44
42
-
<dependencies>
43
-
<dependency>
44
-
<groupId>com.udojava</groupId>
45
-
<artifactId>EvalEx</artifactId>
46
-
<!-- change to desired version -->
47
-
<version>2.7</version>
48
-
</dependency>
49
-
</dependencies>
50
-
````
51
-
52
-
If you're using gradle add to your project's app build.gradle:
53
-
54
-
````gradle
55
-
dependencies {
56
-
...
57
-
compile 'com.udojava:EvalEx:2.7'
58
-
}
59
-
````
60
-
61
-
### FAQ
62
-
63
-
A list of frequently asked questions (and answers) can be found
64
-
here: [FAQ](https://github.com/uklimaschewski/EvalEx/blob/master/FAQ.md)
65
-
66
-
### Usage Examples
67
-
68
-
````java
69
-
BigDecimal result=null;
45
+
```java
46
+
Expression expression = new Expression("1 + 2 / (4 * SQRT(4))");
70
47
71
-
// Simple usage with an expression without variables.
72
-
Expression expression=new Expression("1+1/3");
73
-
result=expression.eval(); // 1.333333
74
-
// Lowering the precision.
75
-
expression.setPrecision(2);
76
-
result=expression.eval(); // 1.3
77
-
78
-
// A more complex expression showing support for unary operators.
79
-
result=new Expression("(3.4 + -4.1)/2").eval(); // -0.35
80
-
81
-
// Using functions and variables.
82
-
result=new Expression("SQRT(a^2 + b^2)")
83
-
.with("a","2.4")
84
-
.and("b","9.253")
85
-
.eval(); // 9.5591845
86
-
87
-
// Using pre-created BigDecimals for variables
88
-
BigDecimal a=new BigDecimal("2.4");
89
-
BigDecimal b=new BigDecimal("9.235");
90
-
result=new Expression("SQRT(a^2 + b^2)")
91
-
.with("a",a)
92
-
.and("b",b)
93
-
.eval(); // 9.5591845
48
+
EvaluationValue result = expression.evaluate();
94
49
95
-
// Increasing the precision and setting a different rounding mode.
96
-
result=new Expression("2.4/PI")
97
-
.setPrecision(128)
98
-
.setRoundingMode(RoundingMode.UP)
99
-
.eval(); // 0.763943726841...
50
+
System.out.println(result.getNumberValue()); // prints 1.25
51
+
```
100
52
101
-
// Using a function to receive a random number and test it.
102
-
result=new Expression("random() > 0.5").eval(); // 1
53
+
### Variables can be specified in the expression and their values can be passed for evaluation:
103
54
104
-
// Using more functions and showing the boolean support.
105
-
result=new Expression("not(x<7 || sqrt(max(x,9,3,min(4,3))) <= 3)")
106
-
.with("x","22.9")
107
-
.eval(); // 1
55
+
```java
56
+
Expression expression = new Expression("(a + b) * (a - b)");
108
57
109
-
// Calling a pre-defined function.
110
-
result=new Expression("log10(100)").eval(); // 2
111
-
````
58
+
EvaluationValue result = expression
59
+
.with("a", 3.5)
60
+
.and("b", 2.5)
61
+
.evaluate();
112
62
113
-
### Precision
63
+
System.out.println(result.getNumberValue()); // prints 6.00
64
+
```
114
65
115
-
The default precision is set to 7 digits (`MathContext.DECIMAL32`). Depending on your use-case you
116
-
will want to set a different precision to get accurate results:
66
+
### Boolean expressions produce a boolean result:
117
67
118
68
```java
119
-
new Expression("1/3")
120
-
.setPrecision(3)
121
-
.eval(); // 0.333
69
+
Expression expression = new Expression("level > 2 || level <= 0");
70
+
71
+
EvaluationValue result = expression
72
+
.with("level", 3.5)
73
+
.evaluate();
122
74
123
-
new Expression("1/3")
124
-
.setPrecision(12)
125
-
.eval(); // 0.333333333333
75
+
System.out.println(result.getBooleanValue()); // prints true
126
76
```
127
77
128
-
If you do not increase the precision as needed, you will get inaccurate results:
78
+
### Like in Java, strings and text can be mixed:
129
79
130
80
```java
131
-
new Expression("123456789 + 123456789").eval(); // 246913600
81
+
Expression expression = new Expression("\"Hello \" + name + \", you are \" + age")
82
+
.with("name","Frank")
83
+
.and("age",38);
132
84
133
-
new Expression("123456789 + 123456789")
134
-
.setPrecision(12)
135
-
.eval(); // 246913578
85
+
System.out.println(expression.evaluate().getStringValue()); // prints Hello Frank, you are 38
136
86
```
137
87
138
-
### Default Settings
139
-
140
-
The default settings for an expression can be set on creation through an `ExpressionSettings`
141
-
object. It can be created using a builder pattern:
142
-
143
-
````java
144
-
ExpressionSettings settings=ExpressionSettings.builder()
145
-
.mathContext(MathContext.DECIMAL128)
146
-
.powerOperatorPrecedenceHigher()
147
-
.build();
148
-
new Expression("-2^2",settings).eval();
149
-
````
150
-
151
-
### Supported Operators
152
-
153
-
<table>
154
-
<tr><th>Mathematical Operators</th></tr>
155
-
<tr><th>Operator</th><th>Description</th></tr>
156
-
<tr><td>+</td><td>Additive operator / Unary plus</td></tr>
157
-
<tr><td>-</td><td>Subtraction operator / Unary minus</td></tr>
158
-
<tr><td>*</td><td>Multiplication operator, can be omitted in front of an open bracket</td></tr>
159
-
<tr><td>/</td><td>Division operator</td></tr>
160
-
<tr><td>%</td><td>Remainder operator (Modulo)</td></tr>
161
-
<tr><td>^</td><td>Power operator</td></tr>
162
-
</table>
163
-
164
-
<table>
165
-
<tr><th>Boolean Operators<sup>*</sup></th></tr>
166
-
<tr><th>Operator</th><th>Description</th></tr>
167
-
<tr><td>=</td><td>Equals</td></tr>
168
-
<tr><td>==</td><td>Equals</td></tr>
169
-
<tr><td>!=</td><td>Not equals</td></tr>
170
-
<tr><td><></td><td>Not equals</td></tr>
171
-
<tr><td><</td><td>Less than</td></tr>
172
-
<tr><td><=</td><td>Less than or equal to</td></tr>
173
-
<tr><td>></td><td>Greater than</td></tr> <tr><td>>=</td><td>Greater than or equal to</td></tr>
174
-
<tr><td>&&</td><td>Boolean and</td></tr>
175
-
<tr><td>||</td><td>Boolean or</td></tr>
176
-
</table>
177
-
*Boolean operators always result in a BigDecimal value of 1 or 0 (zero). Any non-zero value is treated as a _true_ value. Boolean _not_ is implemented by a function.
178
-
179
-
### Supported Functions
180
-
181
-
<table>
182
-
<tr><th>Function<sup>*</sup></th><th>Description</th></tr>
183
-
<tr><td>NOT(<i>expression</i>)</td><td>Boolean negation, 1 (means true) if the expression is not zero</td></tr>
184
-
<tr><td>IF(<i>condition</i>,<i>value_if_true</i>,<i>value_if_false</i>)</td><td>Returns one value if the condition evaluates to true or the other if it evaluates to false</td></tr>
185
-
<tr><td>RANDOM()</td><td>Produces a random number between 0 and 1</td></tr>
186
-
<tr><td>MIN(<i>e1</i>,<i>e2</i>, <i>...</i>)</td><td>Returns the smallest of the given expressions</td></tr>
187
-
<tr><td>MAX(<i>e1</i>,<i>e2</i>, <i>...</i>)</td><td>Returns the biggest of the given expressions</td></tr>
188
-
<tr><td>ABS(<i>expression</i>)</td><td>Returns the absolute (non-negative) value of the expression</td></tr>
189
-
<tr><td>ROUND(<i>expression</i>,precision)</td><td>Rounds a value to a certain number of digits, uses the current rounding mode</td></tr>
190
-
<tr><td>FLOOR(<i>expression</i>)</td><td>Rounds the value down to the nearest integer</td></tr>
191
-
<tr><td>CEILING(<i>expression</i>)</td><td>Rounds the value up to the nearest integer</td></tr>
192
-
<tr><td>LOG(<i>expression</i>)</td><td>Returns the natural logarithm (base e) of an expression</td></tr>
193
-
<tr><td>LOG10(<i>expression</i>)</td><td>Returns the common logarithm (base 10) of an expression</td></tr>
194
-
<tr><td>SQRT(<i>expression</i>)</td><td>Returns the square root of an expression</td></tr>
195
-
<tr><td>SINR(<i>expression</i>)</td><td>Returns the trigonometric sine of an angle (in radians)</td></tr>
196
-
<tr><td>COSR(<i>expression</i>)</td><td>Returns the trigonometric cosine of an angle (in radians)</td></tr>
197
-
<tr><td>TANR(<i>expression</i>)</td><td>Returns the trigonometric tangensuiju of an angle (in radians)</td></tr>
198
-
<tr><td>COTR(<i>expression</i>)</td><td>Returns the trigonometric cotangens of an angle (in radians)</td></tr>
199
-
<tr><td>SECR(<i>expression</i>)</td><td>Returns the secant (in radians)</td></tr>
200
-
<tr><td>CSCR(<i>expression</i>)</td><td>Returns the cosecant (in radians)</td></tr>
201
-
<tr><td>ASINR(<i>expression</i>)</td><td>Returns the angle of asin (in radians)</td></tr>
202
-
<tr><td>ACOSR(<i>expression</i>)</td><td>Returns the angle of acos (in radians)</td></tr>
203
-
<tr><td>ATANR(<i>expression</i>)</td><td>Returns the angle of atan (in radians)</td></tr>
204
-
<tr><td>ACOTR(<i>expression</i>)</td><td>Returns the angle of acot (in radians)</td></tr>
205
-
<tr><td>ATAN2R(<i>y</i>,<i>x</i>)</td><td>Returns the angle of atan2 (in radians)</td></tr>
206
-
<tr><td>SIN(<i>expression</i>)</td><td>Returns the trigonometric sine of an angle (in degrees)</td></tr>
207
-
<tr><td>COS(<i>expression</i>)</td><td>Returns the trigonometric cosine of an angle (in degrees)</td></tr>
208
-
<tr><td>TAN(<i>expression</i>)</td><td>Returns the trigonometric tangens of an angle (in degrees)</td></tr>
209
-
<tr><td>COT(<i>expression</i>)</td><td>Returns the trigonometric cotangens of an angle (in degrees)</td></tr>
210
-
<tr><td>SEC(<i>expression</i>)</td><td>Returns the secant (in degrees)</td></tr>
211
-
<tr><td>CSC(<i>expression</i>)</td><td>Returns the cosecant (in degrees)</td></tr>
212
-
<tr><td>ASIN(<i>expression</i>)</td><td>Returns the angle of asin (in degrees)</td></tr>
213
-
<tr><td>ACOS(<i>expression</i>)</td><td>Returns the angle of acos (in degrees)</td></tr>
214
-
<tr><td>ATAN(<i>expression</i>)</td><td>Returns the angle of atan (in degrees)</td></tr>
215
-
<tr><td>ACOT(<i>expression</i>)</td><td>Returns the angle of acot (in degrees)</td></tr>
216
-
<tr><td>ATAN2(<i>y</i>,<i>x</i>)</td><td>Returns the angle of atan2 (in degrees)</td></tr>
217
-
<tr><td>SINH(<i>expression</i>)</td><td>Returns the hyperbolic sine of a value</td></tr>
218
-
<tr><td>COSH(<i>expression</i>)</td><td>Returns the hyperbolic cosine of a value</td></tr>
219
-
<tr><td>TANH(<i>expression</i>)</td><td>Returns the hyperbolic tangens of a value</td></tr>
220
-
<tr><td>COTH(<i>expression</i>)</td><td>Returns the hyperbolic cotangens of a value</td></tr>
221
-
<tr><td>SECH(<i>expression</i>)</td><td>Returns the hyperbolic secant (in degrees)</td></tr>
222
-
<tr><td>CSCH(<i>expression</i>)</td><td>Returns the hyperbolic cosecant (in degrees)</td></tr>
223
-
<tr><td>ASINH(<i>expression</i>)</td><td>Returns the angle of hyperbolic sine (in degrees)</td></tr>
224
-
<tr><td>ACOSH(<i>expression</i>)</td><td>Returns the angle of hyperbolic cosine (in degrees)</td></tr>
225
-
<tr><td>ATANH(<i>expression</i>)</td><td>Returns the angle of hyperbolic tangens of a value</td></tr>
226
-
<tr><td>RAD(<i>expression</i>)</td><td>Converts an angle measured in degrees to an approximately equivalent angle measured in radians</td></tr>
227
-
<tr><td>DEG(<i>expression</i>)</td><td>Converts an angle measured in radians to an approximately equivalent angle measured in degrees</td></tr>
228
-
<tr><td>FACT(<i>expression</i>)</td><td>Retuns the factorial value of an integer. Will return 1 for 0 or a negative number</td></tr>
229
-
</table>
230
-
*Functions names are case insensitive.
88
+
### Arrays are supported and can be passed as Java _Lists_.
89
+
See the [Documentation](https://ezylang.github.io/pages-playground/concepts/datatypes.html#array)
90
+
for more details.
231
91
232
-
### Supported Constants
233
-
234
-
<table>
235
-
<tr><th>Constant</th><th>Description</th></tr>
236
-
<tr><td>e</td><td>The value of <i>e</i>, exact to 70 digits</td></tr>
237
-
<tr><td>PI</td><td>The value of <i>PI</i>, exact to 100 digits</td></tr>
238
-
<tr><td>TRUE</td><td>The value one</td></tr>
239
-
<tr><td>FALSE</td><td>The value zero</td></tr>
240
-
<tr><td>NULL</td><td>The null value</td></tr>
241
-
</table>
242
-
243
-
### Add Custom Operators
244
-
245
-
Custom operators can be added easily, simply create an instance of `Expression.Operator` and add it
246
-
to the expression. Parameters are the operator string, its precedence and if it is left associative.
247
-
The operators `eval()` method will be called with the BigDecimal values of the operands. All
248
-
existing operators can also be overridden.
249
-
250
-
For example, add an operator `x >> n`, that moves the decimal point of _x_ _n_ digits to the right:
251
-
252
-
````java
253
-
Expression e=new Expression("2.1234 >> 2");
254
-
255
-
e.addOperator(new AbstractOperator(">>",30,true){
256
-
@Override
257
-
public BigDecimal eval(BigDecimal v1,BigDecimal v2){
258
-
return v1.movePointRight(v2.toBigInteger().intValue());
259
-
}
260
-
});
261
-
262
-
e.eval(); // returns 212.34
263
-
````
264
-
265
-
Or another example, add a postfix unary operator `n!`, that calculates the factorial of n. The
266
-
parameters for postfix unary operators are the operator's string, its precedence, if it is left
267
-
associative, is it is boolean and if it is unary (<code>true</code>).
268
-
269
-
````java
270
-
Expression e=new Expression("4!");
271
-
272
-
e.addOperator(new AbstractOperator("!",Expression.OPERATOR_PRECEDENCE_POWER_HIGHER+1,true,false,true){
273
-
@Override
274
-
public BigDecimal eval(BigDecimal v1,BigDecimal v2){
275
-
if(v1==null){
276
-
throw new ArithmeticException("Operand may not be null");
277
-
}
278
-
if(v1.remainder(BigDecimal.ONE)!=BigDecimal.ZERO){
279
-
throw new ArithmeticException("Operand must be an integer");
280
-
}
281
-
BigDecimal factorial=v1;
282
-
v1=v1.subtract(BigDecimal.ONE);
283
-
if(factorial.compareTo(BigDecimal.ZERO)==0||factorial.compareTo(BigDecimal.ONE)==0){
284
-
return BigDecimal.ONE;
285
-
}else{
286
-
while(v1.compareTo(BigDecimal.ONE)>0){
287
-
factorial=factorial.multiply(v1);
288
-
v1=v1.subtract(BigDecimal.ONE);
289
-
}
290
-
return factorial;
291
-
}
292
-
}
293
-
});
294
-
295
-
e.eval(); // returns 24
296
-
````
297
-
298
-
### Add Custom Functions
299
-
300
-
Adding custom functions is as easy as adding custom operators. Create an instance
301
-
of `Expression.Function`and add it to the expression. Parameters are the function name and the count
302
-
of required parameters. The functions `eval()` method will be called with a list of the BigDecimal
303
-
parameters. A `-1` as the number of parameters denotes a variable number of arguments. All existing
304
-
functions can also be overridden.
305
-
306
-
For example, add a function `average(a,b,c)`, that will calculate the average value of a, b and c:
307
-
308
-
````java
309
-
Expression e=new Expression("2 * average(12,4,8)");
310
-
311
-
e.addFunction(new AbstractFunction("average",-1){
312
-
@Override
313
-
public BigDecimal eval(List<BigDecimal> parameters){
314
-
if(parameters.size()==0){
315
-
throw new ExpressionException("average requires at least one parameter");
316
-
}
317
-
BigDecimal avg=new BigDecimal(0);
318
-
for(BigDecimal parameter:parameters){
319
-
avg=avg.add(parameter);
320
-
}
321
-
return avg.divide(new BigDecimal(parameters.size()));
322
-
}
323
-
});
92
+
```java
93
+
Expression expression = new Expression("values[i-1] * factors[i-1]");
324
94
325
-
e.eval(); // returns 16
326
-
````
95
+
EvaluationValue result = expression
96
+
.with("values", List.of(2, 3, 4))
97
+
.and("factors", List.of(2, 4, 6))
98
+
.and("i", 1)
99
+
.evaluate();
327
100
328
-
#### Custom Functions With String Parameters
101
+
System.out.println(result.getNumberValue()); // prints 4
102
+
```
329
103
330
-
You can create a custom function with string parameters. Create an instance
331
-
of `Expression.LazyFunction`and add it to the expression. Parameters are the function name and the
332
-
count of required parameters. The functions `lazyEval()` method will be called with a list of the
333
-
LazyNumber parameters. A `-1` as the number of parameters denotes a variable number of arguments.
334
-
String parameters needs to be surrounded by `"`.
104
+
### Structures are supported and can be passed as Java _Maps_.
105
+
Arrays and Structures can be combined to build arbitrary data structures. See
106
+
the [Documentation](https://ezylang.github.io/pages-playground/concepts/datatypes.html#structure)
107
+
for more details.
335
108
336
-
For example, add a function `STREQ("string1","string2")`, that will compare whether string1 and
337
-
string2 are equal:
109
+
```java
110
+
Map<String, Object> order = new HashMap<>();
111
+
order.put("id", 12345);
112
+
order.put("name", "Mary");
338
113
339
-
````java
340
-
Expression e=new Expression("STREQ(\"test\", \"test2\")");
341
-
e.addLazyFunction(new AbstractLazyFunction("STREQ",2){
342
-
private LazyNumber ZERO=new LazyNumber(){
343
-
public BigDecimal eval(){
344
-
return BigDecimal.ZERO;
345
-
}
346
-
public String getString(){
347
-
return"0";
348
-
}
349
-
};
350
-
private LazyNumber ONE=new LazyNumber(){
351
-
public BigDecimal eval(){
352
-
return BigDecimal.ONE;
353
-
}
354
-
public String getString(){
355
-
return null;
356
-
}
357
-
};
358
-
@Override
359
-
public LazyNumber lazyEval(List<LazyNumber> lazyParams){
360
-
if(lazyParams.get(0).getString().equals(lazyParams.get(1).getString())){
361
-
return ONE;
362
-
}
363
-
return ZERO;
364
-
}
365
-
});
114
+
Map<String, Object> position = new HashMap<>();
115
+
position.put("article", 3114);
116
+
position.put("amount", 3);
117
+
position.put("price", new BigDecimal("14.95"));
366
118
367
-
e.eval(); // returns 0
368
-
````
119
+
order.put("positions", List.of(position));
369
120
370
-
### How to contribute
121
+
Expression expression = new Expression("order.positions[x].amount * order.positions[x].price")
122
+
.with("order", order)
123
+
.and("x", 0);
371
124
372
-
#### How to make a clean pull request
125
+
BigDecimal result = expression.evaluate().getNumberValue();
373
126
374
-
- Create a personal fork of EvalEx on GitHub.
375
-
- Clone the fork on your local machine. Your remote repo on GitHub is called origin.
376
-
- Add the original repository as a remote called upstream.
377
-
- If you created your fork a while ago be sure to pull upstream changes into your local repository.
378
-
- Create a new branch to work on. Branch from master.
379
-
- Implement/fix your feature, comment your code.
380
-
- Follow the code style of EvalEx (Google code style), including indentation.
381
-
- If the project has tests run them!
382
-
- Add unit tests that test your new code.
383
-
- In general, avoid changing existing tests, as they also make sure the existing public API is
384
-
unchanged.
385
-
- Add or change the documentation as needed.
386
-
- Squash your commits into a single commit with git's interactive rebase.
387
-
- Push your branch to your fork on GitHub, the remote origin.
388
-
- From your fork open a pull request in the correct branch. Target the EvalEx's master branch.
389
-
- Once the pull request is approved and merged you can pull the changes from upstream to your local
390
-
repo and delete your branch.
391
-
- Last but not least: Always write your commit messages in the present tense. Your commit message
392
-
should describe what the commit, when applied, does to the code – not what you did to the code.
127
+
System.out.println(result); // prints 44.85
128
+
```
393
129
394
-
### Author and License
130
+
## Author and License
395
131
396
-
Copyright 2012-2021 by Udo Klimaschewski
132
+
Copyright 2012-2022 by Udo Klimaschewski
397
133
398
134
**Thanks to all who contributed to this
399
-
project: [Contributors](https://github.com/uklimaschewski/EvalEx/graphs/contributors)**
135
+
project: [Contributors](https://github.com/ezylang/EvalEx/graphs/contributors)**
400
136
401
-
The software is licensed under the MIT Open Source license (
402
-
see [LICENSE](https://raw.githubusercontent.com/uklimaschewski/EvalEx/master/LICENSE) file).
137
+
The software is licensed under the Apache License, Version 2.0 (
138
+
see [LICENSE](https://raw.githubusercontent.com/ezylang/EvalEx/main/LICENSE) file).
403
139
404
140
* The *power of* operator (^) implementation was copied
405
141
from [Stack Overflow](http://stackoverflow.com/questions/3579779/how-to-do-a-fractional-power-on-bigdecimal-in-java)
···
407
143
* The SQRT() function implementation was taken from the
408
144
book [The Java Programmers Guide To numerical Computing](http://www.amazon.de/Java-Number-Cruncher-Programmers-Numerical/dp/0130460419) (
409
145
Ronald Mak, 2002)
410
-
* Varargs implementation based on "David's method" outlined in Gene Pavlovsky's comment
411
-
from [here](http://www.kallisti.net.nz/blog/2008/02/extension-to-the-shunting-yard-algorithm-to-allow-variable-numbers-of-arguments-to-functions/#comment-125789)
412
-
413
-
### Other projects based on EvalEx
414
-
415
-
* [A port of EvalEx to Dart](https://github.com/RobluScouting/EvalEx)
-49
deploy.sh
-49
deploy.sh
···
1
-
#!/bin/bash
2
-
# expects variables to be set:
3
-
# - OSSRH_USERNAME
4
-
# - OSSRH_PASSWORD
5
-
# - GPG_KEY_NAME
6
-
# - GPG_PASSPHRASE
7
-
# expects file to exist:
8
-
# - .travis/gpg.asc
9
-
10
-
set -e
11
-
12
-
echo "starting deploy script deploy.sh ..."
13
-
14
-
# Check the variables are set
15
-
if [ -z "$OSSRH_USERNAME" ]; then
16
-
echo "missing environment value: OSSRH_USERNAME" >&2
17
-
exit 1
18
-
fi
19
-
20
-
if [ -z "$OSSRH_PASSWORD" ]; then
21
-
echo "missing environment value: OSSRH_PASSWORD" >&2
22
-
exit 1
23
-
fi
24
-
25
-
if [ -z "$GPG_KEY_NAME" ]; then
26
-
echo "missing environment value: GPG_KEY_NAME" >&2
27
-
exit 1
28
-
fi
29
-
30
-
if [ -z "$GPG_PASSPHRASE" ]; then
31
-
echo "missing environment value: GPG_PASSPHRASE" >&2
32
-
exit 1
33
-
fi
34
-
35
-
# Prepare the local keyring (requires travis to have decrypted the file
36
-
# beforehand)
37
-
echo "importing gpg keys"
38
-
gpg -v --fast-import .travis/gpg.asc
39
-
40
-
if [ -n "$TRAVIS_TAG" ]
41
-
then
42
-
echo "on a tag -> set pom.xml <version> to $TRAVIS_TAG"
43
-
mvn --settings "${TRAVIS_BUILD_DIR}/.travis/mvn-settings.xml" org.codehaus.mojo:versions-maven-plugin:2.1:set -DnewVersion="$TRAVIS_TAG" 1>/dev/null 2>/dev/null
44
-
else
45
-
echo "not on a tag -> keep snapshot version in pom.xml" >&2
46
-
fi
47
-
48
-
# Run the maven deploy steps
49
-
mvn deploy -P publish -DskipTests=true --settings "${TRAVIS_BUILD_DIR}/.travis/mvn-settings.xml"
+18
docs/README.md
+18
docs/README.md
···
1
+
# EvalEx Documentation
2
+
3
+
For documentation, EvalEx uses GitHub Pages, which uses Jekyll to transform markdown to static HTML.
4
+
5
+
The site HTML is generated whenever something is committed to the main branch.
6
+
7
+
Jekyll will then transform all files it finds in the "docs" directory.
8
+
9
+
For the layout the JustTheDocs template is used, which supports a lot of good things like a sidebar
10
+
menu, page hierarchy and searching.
11
+
12
+
## EvalEx documentation home:
13
+
14
+
https://ezylang.github.io/EvalEx/
15
+
16
+
## Using JustTheDocs template:
17
+
18
+
https://just-the-docs.github.io/just-the-docs/
+6
docs/_config.yml
+6
docs/_config.yml
+6
docs/concepts/concepts.md
+6
docs/concepts/concepts.md
+167
docs/concepts/datatypes.md
+167
docs/concepts/datatypes.md
···
1
+
---
2
+
layout: default
3
+
title: Data types
4
+
parent: Concepts
5
+
nav_order: 2
6
+
---
7
+
8
+
## Data Types
9
+
10
+
EvalEx supports the following data types:
11
+
12
+
| Data Type | Internal Representation |
13
+
|-----------------|-----------------------------------|
14
+
| NUMBER | java.math.BigDecimal |
15
+
| BOOLEAN | java.lang.Boolean |
16
+
| STRING | java.lang.String |
17
+
| ARRAY | java.util.List |
18
+
| STRUCTURE | java.util.Map |
19
+
| EXPRESSION_NODE | com.ezylang.evalex.parser.ASTNode |
20
+
21
+
Data is stored in an _EvaluationValue_, which holds the value and the data type.
22
+
23
+
### NUMBER
24
+
25
+
Numbers are stored as _java.math.BigDecimal_ values and are either passed directly, or will
26
+
automatically be converted from one of the following Java data types:
27
+
28
+
- long
29
+
- int
30
+
- short
31
+
- byte
32
+
- double
33
+
- float
34
+
35
+
Be careful when passing double or float values, as these have to be converted from their
36
+
floating-point arithmetic representation to a BigDecimal representation, which stores values a
37
+
signed decimal number of arbitrary precision with an associated scale.
38
+
39
+
### BOOLEAN
40
+
41
+
Booleans can be passed directly as variable values to an expression evaluation, or are the result of
42
+
a boolean operation or function.
43
+
44
+
If _STRING_ or _NUMBER_ values are used in boolean expressions, they are converted using the
45
+
following rules:
46
+
47
+
- If the value is a _NUMBER_, it is false if it equals to zero. All other values evaluate to true.
48
+
- If the value is a _STRING_, it is true if it equals to "true" (any case), false otherwise.
49
+
50
+
Consider this example, it will evaluate to true:
51
+
52
+
```java
53
+
Expression expression = new Expression("stringValue && numberValue")
54
+
.with("stringValue", "True")
55
+
.and("numberValue", 42);
56
+
```
57
+
58
+
### STRING
59
+
60
+
Any instance of _java.lang.CharSequence_ or _java.lang.Character_ will automatically be converted to
61
+
a _STRING_ datatype. Conversion will be done by invoking the _toString()_ method on the input
62
+
object.
63
+
64
+
### ARRAY
65
+
66
+
Arrays are stored internally as a _java.util.List<EvaluationValue>_. When passed as a
67
+
variable, the list will be iterated and each entry will be converted using the data type conversion
68
+
rules.
69
+
So, for example, a list of double values will be converted to a list of _EvaluationValue_
70
+
objects of type _NUMBER_, with an internal BigDecimal representation.
71
+
72
+
Arrays can hold mixed data types:
73
+
74
+
```java
75
+
List<Object> list = List.of(2.5, "Hello", true);
76
+
77
+
// prints "true"
78
+
System.out.println(
79
+
new Expression("list[2]").with("list", list).evaluate().getStringValue());
80
+
```
81
+
82
+
Array index starts at 0 with the first array element.
83
+
84
+
Arrays can hold other arrays as members, which can hold other arrays and so on:
85
+
86
+
```java
87
+
List<Object> list1 = List.of(1, 2, 3);
88
+
List<Object> list2 = List.of(4, 5, 6);
89
+
List<Object> sublist = List.of(100, 200, 300);
90
+
List<Object> list3 = List.of(7, 8, sublist);
91
+
92
+
List list = List.of(list1, list2, list3);
93
+
94
+
// prints 200
95
+
System.out.println(
96
+
new Expression("list[2][2][1]").with("list", list).evaluate().getStringValue());
97
+
```
98
+
99
+
Arrays can also hold _STRUCTURE_ elements as entries.
100
+
101
+
### STRUCTURE
102
+
103
+
Structures are stored internally as a _java.util.Map<String, EvaluationValue>_. When passed as
104
+
a variable, the map entries will be iterated and each entry value will be converted using the data
105
+
type conversion rules.
106
+
107
+
Structures can hold other structures, which can form a tree like data structure.
108
+
109
+
Arrays and structures can be combined in any arbitrary way.
110
+
111
+
```java
112
+
Map<String, Object> order = new HashMap<>();
113
+
order.put("id", 12345);
114
+
order.put("name", "Mary");
115
+
116
+
Map<String, Object> position = new HashMap<>();
117
+
position.put("article", 3114);
118
+
position.put("amount", 3);
119
+
position.put("price", new BigDecimal("14.95"));
120
+
121
+
order.put("positions", List.of(position));
122
+
123
+
Expression expression = new Expression("order.positions[x].amount * order.positions[x].price")
124
+
.with("order", order)
125
+
.and("x", 0);
126
+
127
+
BigDecimal result = expression.evaluate().getNumberValue();
128
+
129
+
System.out.println(result); // prints 44.85
130
+
```
131
+
132
+
### EXPRESSION_NODE
133
+
134
+
A string expression is converted into an abstract syntax tree (AST), which represents the expression
135
+
structure and evaluation order. When an expression is evaluated, the evaluation starts at the lowest
136
+
tree (leaf) node and works its way up to the root node, which then gives the expression result
137
+
value.
138
+
139
+
The internal object for such a node is the _com.ezylang.evalex.parser.ASTNode_. It contains the
140
+
token associated to the node (e.g. a multiplication operator, or a function name) and has zero or
141
+
more children (the operator or function parameters).
142
+
143
+
By passing an _ASTNode_ and its substructure as a variable, this will be handled as a subtree and
144
+
will be evaluated when the node is evaluated.
145
+
146
+
With this, a _lazy evaluation_ is possible, only evaluating a node when it is desired (see the
147
+
_IF()_ function for an example. Also, it allows the creation of sub-expressions.
148
+
149
+
The _Expression_ object has a method to create such sub-expressions, returning the root node of an
150
+
arbitrary string expression:
151
+
152
+
```java
153
+
Expression expression = new Expression("a*b");
154
+
155
+
ASTNode subExpression = expression.createExpressionNode("4+3");
156
+
157
+
EvaluationValue result = expression
158
+
.with("a", 2)
159
+
.and("b", subExpression)
160
+
.evaluate();
161
+
162
+
System.out.println(result); // prints 14
163
+
```
164
+
165
+
Note that the above expression is not evaluated as "2 * 4 + 3", which would result in 11.
166
+
Instead, the sub-expression "4 + 3" is calculated first, when it comes to finding the value of the
167
+
variable _b_. Resulting in calculation of "2 * 7", which is 14.
+134
docs/concepts/parsing_evaluation.md
+134
docs/concepts/parsing_evaluation.md
···
1
+
---
2
+
layout: default
3
+
title: Parsing and Evaluation
4
+
parent: Concepts
5
+
nav_order: 1
6
+
---
7
+
8
+
## Parsing and Evaluation
9
+
10
+
### Parsing
11
+
12
+
To parse and evaluate an expression, you first create an expression object:
13
+
14
+
```java
15
+
Expression expression = new Expression("1 + 2 / (4 * SQRT(4))");
16
+
```
17
+
18
+
Optionally, you can create a custom configuration and pass this in the constructor:
19
+
20
+
```java
21
+
ExpressionConfiguration configuration = ExpressionConfiguration.builder()
22
+
.decimalPlacesRounding(2)
23
+
.build();
24
+
25
+
Expression expression = new Expression("1 + 2 / (4 * SQRT(4))", configuration);
26
+
```
27
+
28
+
Creating the expression object does not parse or validate the expression.
29
+
30
+
Parsing will be performed, when one of the following happens:
31
+
32
+
- The expression is validated wih the _validate()_ method.
33
+
- The expression is evaluated with the _evaluate()_ method.
34
+
- An abstract syntax tree (AST) is created with the _getAbstractSyntaxTree()_ method or any public
35
+
method that uses it.
36
+
37
+
Once an expression was parsed, the resulting abstract syntax tree is cached inside the expression
38
+
and wil be re-used on subsequent evaluations.
39
+
40
+
### Evaluation
41
+
42
+
Evaluation is done by traversing the parsed abstract syntax tree. If it has not been generated
43
+
before, it will be created and cached prior to the evaluation.
44
+
45
+
```java
46
+
Expression expression = new Expression("1 + 2 / (4 * SQRT(4))");
47
+
48
+
EvaluationValue result = expression.evaluate();
49
+
50
+
System.out.println(result.getNumberValue()); // prints 1.25
51
+
```
52
+
53
+
The evaluation will return an _EvaluationValue_ object. Depending on the expression, the
54
+
_EvaluationValue_ will be of one of the types:
55
+
56
+
- NUMBER - If the expression resulted in a number.
57
+
- STRING - If the expression resulted in a string.
58
+
- BOOLEAN - If the expression resulted in a boolean value.
59
+
- ARRAY - If the expression resulted in an array.
60
+
- STRUCTURE - If the expression resulted in a structure.
61
+
62
+
The _EvaluationValue_ has methods to check and retrieve/convert the evaluation value.
63
+
64
+
See chapter [Data Types](datatypes.html) for details on the different data types.
65
+
66
+
### Validation
67
+
68
+
An expression can be validated with the _validate()_ method. If there is a parsing problem, the
69
+
method will throw a _ParseException_, which holds detailed information about the problem and its
70
+
location.
71
+
72
+
### Passing Variables
73
+
74
+
Variables can be used in an expression. Their value will be retrieved during evaluation by querying
75
+
the defined data accessor.
76
+
77
+
by default, EvalEx uses the _MapBasedDataAccessor_, which stores the variable values in a
78
+
case-insensitive map. Each expression will have an own instance of the accessor, so that different
79
+
expression do not share the same data.
80
+
81
+
Prior to evaluation, the variable values can be set by setting them one by one, using the _with()_
82
+
and _and()_ methods.
83
+
(Both methods are the same, the name difference is just for convenience).
84
+
85
+
```java
86
+
Expression expression = new Expression("(a + b) * (a - b)");
87
+
88
+
EvaluationValue result = expression
89
+
.with("a", 3.5)
90
+
.and("b", 2.5)
91
+
.evaluate();
92
+
93
+
System.out.println(result.getNumberValue()); // prints 6.00
94
+
```
95
+
96
+
Alternatively, the variable values can be set by defining a map with names and values and then
97
+
passing it to
98
+
the _withValues()_ method:
99
+
100
+
```java
101
+
Expression expression = new Expression("(a + b) * (a - b)");
102
+
103
+
Map<String, Object> values = new HashMap<>();
104
+
values.put("a", 3.5);
105
+
values.put("b", 2.5);
106
+
107
+
EvaluationValue result = expression.withValues(values).evaluate();
108
+
109
+
System.out.println(result.getNumberValue()); // prints 6.00
110
+
```
111
+
112
+
The data conversion of the passed values will automatically be performed through the created
113
+
_EvaluationObject_.
114
+
115
+
See chapter [Data Types](datatypes.html) for details on the conversion.
116
+
117
+
Another option to have EvalEx use your data is to define a custom data accessor.
118
+
119
+
See chapter [Data Access](../customization/data_access.html) for details.
120
+
121
+
### Exception Handling
122
+
123
+
In EvalEx, there are two general exceptions:
124
+
125
+
- A _ParseException_ is thrown, when the expression could not be parsed.
126
+
- An _EvaluationException_ is thrown, when the expression could be parsed, but not evaluated.
127
+
128
+
If possible, both exceptions report the following details:
129
+
130
+
- Start Position of the error (character position, starting with 1).
131
+
- End Position of the error (character position, starting with 1).
132
+
- The Token string, usually the operator, function, variable or literal causing the error.
133
+
- The error message.
134
+
+89
docs/concepts/rounding.md
+89
docs/concepts/rounding.md
···
1
+
---
2
+
layout: default
3
+
title: Precision, Scale and Rounding
4
+
parent: Concepts
5
+
nav_order: 3
6
+
---
7
+
8
+
## Precision, Scale and Rounding
9
+
10
+
EvalEx uses _java.math.BigDecimal_ values for (most) calculations and for internal storage of number
11
+
values and variables.
12
+
13
+
In contrast to float or double values, which are stored in floating-point arithmetic, BigDecimal
14
+
values are stored as signed decimal number of arbitrary precision with an associated scale.
15
+
16
+
This makes BigDecimal a perfect choice in applications, where the precise decimal values are needed,
17
+
e.g. in financial systems.
18
+
19
+
### Precision
20
+
21
+
In BigDecimal (and EvalEx), the precision is the number of digits in the unscaled value, independent
22
+
of the number of decimal digits.
23
+
Both of this values
24
+
25
+
- 123.456
26
+
- 1234.56
27
+
28
+
have a precision of 6, but have a different scale (the first has a scale of 3, the second of 2).
29
+
30
+
Values that exceed the maximum precision, will be rounded according to the rounding mode.
31
+
32
+
The default precision in EvalEx is 68.
33
+
34
+
### Scale
35
+
36
+
The scale of a BigDecimal is the number of decimal digits. Any BigDecimal can be rounded to a lower
37
+
number of scale (less decimal digits) using a rounding mode.
38
+
39
+
### Rounding Mode
40
+
41
+
EvalEx supports all rounding modes defined in _java.math.RoundingMode_:
42
+
43
+
| Mode | Description |
44
+
|-------------|----------------------------------------------------------------------------------------------------------------------------------------------|
45
+
| CEILING | Rounding mode to round towards positive infinity. |
46
+
| DOWN | Rounding mode to round towards zero. |
47
+
| FLOOR | Rounding mode to round towards negative infinity. |
48
+
| HALF_DOWN | Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round down. |
49
+
| HALF_EVEN | Rounding mode to round towards the "nearest neighbor" unless both neighbors are equidistant, in which case, round towards the even neighbor. |
50
+
| HALF_UP | Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round up. |
51
+
| UNNECESSARY | Rounding mode to assert that the requested operation has an exact result, hence no rounding is necessary. |
52
+
| UP | Rounding mode to round away from zero. |
53
+
54
+
The default rounding mode in EvalEx is _HALF_EVEN_.
55
+
56
+
### Configuring Precision, Rounding Mode and Automatic Scaling
57
+
58
+
Precision and rounding mode are configured through the _ExpressionConfiguration_ by specifying
59
+
the _java.math.MathContext_:
60
+
61
+
```java
62
+
// set precision to 32 and rounding mode to HALF_UP
63
+
ExpressionConfiguration configuration =
64
+
ExpressionConfiguration.builder()
65
+
.mathContext(new MathContext(32, RoundingMode.HALF_UP))
66
+
.build();
67
+
```
68
+
69
+
Automatic scaling is disabled by default. When enabled, EvalEx will round all input variables,
70
+
intermediate operation and function results and the final result to the specified number of decimal
71
+
digits, using the current rounding mode:
72
+
73
+
```java
74
+
// set precision to 32 and rounding mode to HALF_UP
75
+
ExpressionConfiguration configuration =
76
+
ExpressionConfiguration.builder()
77
+
.decimalPlacesRounding(2)
78
+
.build();
79
+
80
+
Expression expression = new Expression("2.128 + a", configuration);
81
+
82
+
// prints 3.26
83
+
System.out.println(
84
+
expression
85
+
.with("a", new BigDecimal("1.128"))
86
+
.evaluate()
87
+
.getNumberValue());
88
+
```
89
+
+128
docs/configuration/configuration.md
+128
docs/configuration/configuration.md
···
1
+
---
2
+
layout: default
3
+
title: Configuration
4
+
nav_order: 3
5
+
---
6
+
7
+
## Configuration
8
+
9
+
EvalEx can be configured through an _ExpressionConfiguration_ object, which can be passed as a
10
+
parameter to the _Expression_ constructor.
11
+
12
+
Example usage, showing all default configuration values:
13
+
14
+
```java
15
+
ExpressionConfiguration configuration = ExpressionConfiguration.builder()
16
+
.arraysAllowed(true)
17
+
.dataAccessorSupplier(MapBasedDataAccessor::new)
18
+
.decimalPlacesRounding(ExpressionConfiguration.DECIMAL_PLACES_ROUNDING_UNLIMITED)
19
+
.defaultConstants(ExpressionConfiguration.StandardConstants)
20
+
.functionDictionary(ExpressionConfiguration.StandardFunctionsDictionary)
21
+
.implicitMultiplicationAllowed(true)
22
+
.mathContext(ExpressionConfiguration.DEFAULT_MATH_CONTEXT)
23
+
.operatorDictionary(ExpressionConfiguration.StandardOperatorsDictionary)
24
+
.powerOfPrecedence(OperatorIfc.OPERATOR_PRECEDENCE_POWER)
25
+
.structuresAllowed(true)
26
+
.build();
27
+
28
+
Expression expression = new Expression("2.128 + a", configuration);
29
+
```
30
+
31
+
### Arrays allowed
32
+
33
+
Specifies if the array index function is allowed (default is true). If set to false, the expression
34
+
will throw a _ParseException_, if there is a '[' is encountered in the expression and also no
35
+
operator or function is defined for this character.
36
+
37
+
### Data Accessor
38
+
39
+
The Data Accessor is responsible for storing and retrieving variable values.
40
+
41
+
See chapter [Data Access](../customization/data_access.html) for details.
42
+
43
+
The default implementation is the _MapBasedDataAccessor_, which stores all variables in a
44
+
**case-insensitive** _Map_.
45
+
46
+
The supplier is called whenever a new Expression is created, so that each expression can own an own
47
+
instance of an accessor. Custom implementations of the supplier and data accessor may allow
48
+
expressions to share the same space.
49
+
50
+
Upon creation of an expression, the configured default constants will be added to the data accessor
51
+
through the set method.
52
+
53
+
### Decimal Places Rounding
54
+
55
+
Specifies the amount of decimal places to round to in each operation or function.
56
+
See chapter [Precision, Scale and Rounding](../concepts/rounding.html) for details.
57
+
58
+
### Default Constants
59
+
60
+
Specifies the default constants that can be used in every expression as a _Map_ with the constant
61
+
name and _EvaluationValue_ as value.
62
+
See the reference chapter for a
63
+
list: [Default Constants](../references/constants.html)
64
+
65
+
### Function Dictionary
66
+
67
+
The function dictionary is used to look up the functions that are used in an expression.
68
+
69
+
See chapter [Function Dictionary](../customization/function_dictionary.html) for details.
70
+
71
+
The default implementation is the _MapBasedFunctionDictionary_, which stores all variables in a
72
+
**case-insensitive** _Map_.
73
+
74
+
### Implicit Multiplication
75
+
76
+
Implicit multiplication automatically adds in expression like "(a+b)(b+c)" the missing
77
+
multiplication operator, so that the expression reads "(a+b) * (b+c)".
78
+
79
+
By default, implicit multiplication is enabled. It can be disabled with this configuration
80
+
parameter.
81
+
82
+
### Math Context
83
+
84
+
The math context is used throughout all operations and functions. The default has a precision of 68
85
+
and a rounding mode of _HALF_EVEN_.
86
+
87
+
See chapter [Precision, Scale and Rounding](../concepts/rounding.html) for details.
88
+
89
+
### Operator Dictionary
90
+
91
+
The operator dictionary is used to look up the operators that are used in an expression.
92
+
93
+
See chapter [Operator Dictionary](../customization/operator_dictionary.html) for details.
94
+
95
+
The default implementation is the _MapBasedOperatorDictionary_, which stores all variables in a
96
+
**case-insensitive** _Map_.
97
+
98
+
### Power Of Precedence
99
+
100
+
In mathematics, there is no general rule which precedence the power-of operator has.
101
+
Consider the expression "-2^2". The expression can have two different results, depending on the
102
+
precedence:
103
+
104
+
- If the precedence is lower than the unary minus, the result will be 4 (-2 * -2).
105
+
- If the precedence is higher than the unary minus, the result will be -4 -(2 * 2).
106
+
107
+
By default, EvalEx uses a lower precedence. You can configure to use a higher precedence by
108
+
specifying it here, or by using a predefined constant:
109
+
110
+
```java
111
+
ExpressionConfiguration configuration = ExpressionConfiguration.builder()
112
+
.powerOfPrecedence(OperatorIfc.OPERATOR_PRECEDENCE_POWER_HIGHER)
113
+
.build();
114
+
115
+
// will now result in -4, instead of 4:
116
+
Expression expression = new Expression("-2^2", configuration);
117
+
```
118
+
119
+
### Strip Trailing Zeros
120
+
121
+
If set to true (default), then the trailing decimal zeros in a number result will be stripped.
122
+
E.g. a _BigDecimal_ result of _9.0_ will become _9_ and _-2.120000_ will become _-2.12_.
123
+
124
+
### Structures Allowed
125
+
126
+
Specifies if the structure separator ('.') operator is allowed (default is true). If set to false,
127
+
the expression will throw a _ParseException_, if the a '.' is encountered in the expression and also
128
+
no operator or function is defined for this character.
+142
docs/customization/custom_functions.md
+142
docs/customization/custom_functions.md
···
1
+
---
2
+
layout: default
3
+
title: Custom Functions
4
+
parent: Customization
5
+
nav_order: 1
6
+
---
7
+
8
+
## Custom Functions
9
+
10
+
EvalEx comes with a predefined set of ready-to use functions.
11
+
A standard collection is added to the default configuration, using the
12
+
_ExpressionConfiguration.StandardFunctionsDictionary_.
13
+
14
+
Function definition is made in two parts:
15
+
16
+
- Defining the function parameters and logic by adding a class that implements the _FunctionIfc_
17
+
interface.
18
+
- Adding the function with a unique name to the function dictionary, so it can be found and used in
19
+
expressions.
20
+
21
+
### Defining the Function
22
+
23
+
To ease some common implementation routines, a custom function usually extends the
24
+
_AbstractFunction_ class.
25
+
As an example, we can look at the _ROUND()_ function:
26
+
27
+
```java
28
+
@FunctionParameter(name = "value")
29
+
@FunctionParameter(name = "scale")
30
+
public class RoundFunction extends AbstractFunction {
31
+
@Override
32
+
public EvaluationValue evaluate(
33
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
34
+
35
+
EvaluationValue value = parameterValues[0];
36
+
EvaluationValue precision = parameterValues[1];
37
+
38
+
return new EvaluationValue(
39
+
value
40
+
.getNumberValue()
41
+
.setScale(
42
+
precision.getNumberValue().intValue(),
43
+
expression.getConfiguration().getMathContext().getRoundingMode()));
44
+
}
45
+
}
46
+
```
47
+
48
+
As you can see, the only method that has to be implemented, is the _evaluate()_ method. It will be
49
+
called by EvalEx during evaluation. It will pass all parameters values to function.
50
+
51
+
The expression itself and also the function token are passed to the call, they can be used to
52
+
access the configuration or to find out the function name and its position in the expression.
53
+
54
+
#### Parameter Definition
55
+
56
+
Parameters are defined by annotating the function class. Each _FunctionParameter_ has at least a
57
+
name.
58
+
In the above example, the function expects two parameters, the value and the scale.
59
+
60
+
#### Variable Number of Arguments
61
+
62
+
Some functions allow a variable number of arguments. For this, a parameter can have the flag _
63
+
isVarArg_ set to _true_.
64
+
65
+
> NOTE: Only the last parameter and only one parameter is allowed to have the _isVarArg_ flag set
66
+
> to _true_.
67
+
68
+
For example, the _MAX()_ function makes use of a variable number of arguments:
69
+
70
+
```java
71
+
@FunctionParameter(name = "value", isVarArg = true)
72
+
public class MaxFunction extends AbstractFunction {
73
+
@Override
74
+
public EvaluationValue evaluate(
75
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
76
+
BigDecimal max = null;
77
+
for (EvaluationValue parameter : parameterValues) {
78
+
if (max == null || parameter.getNumberValue().compareTo(max) > 0) {
79
+
max = parameter.getNumberValue();
80
+
}
81
+
}
82
+
return new EvaluationValue(max);
83
+
}
84
+
}
85
+
```
86
+
87
+
#### Lazy Parameter Evaluation
88
+
89
+
Usually, a function parameter already holds the evaluated value. E.g. in "ROUND(4/3, 2)", the
90
+
function _evaluate()_ will be passed the already calculated result of "4/3".
91
+
92
+
In some cases, this is not useful. Take for example the _IF()_ function. It has three parameters:
93
+
94
+
- A boolean condition
95
+
- An expression value that is returned if the condition is _true_
96
+
- An expression value that is returned if the condition is _false_
97
+
98
+
Here not both expressions should be evaluated, before the function is called. This may lead to
99
+
errors, e.g. in "IF(a == 0, 0, 2 / a)" the evaluation of "2 / a" would lead to a division by zero
100
+
error.
101
+
102
+
Therefore, function parameters can be defined as "lazy", which means that they will not be
103
+
evaluated, before the function is called. Instead, the expression sub-nodes are passed as a
104
+
parameter and the function is responsible to evaluate the subexpression.
105
+
106
+
This can be easily done, as the _IF()_ function demonstrates:
107
+
108
+
```java
109
+
@FunctionParameter(name = "condition")
110
+
@FunctionParameter(name = "resultIfTrue", isLazy = true)
111
+
@FunctionParameter(name = "resultIfFalse", isLazy = true)
112
+
public class IfFunction extends AbstractFunction {
113
+
@Override
114
+
public EvaluationValue evaluate(
115
+
Expression expression, Token functionToken, EvaluationValue... parameterValues)
116
+
throws EvaluationException {
117
+
if (parameterValues[0].getBooleanValue()) {
118
+
return expression.evaluateSubtree(parameterValues[1].getExpressionNode());
119
+
} else {
120
+
return expression.evaluateSubtree(parameterValues[2].getExpressionNode());
121
+
}
122
+
}
123
+
}
124
+
```
125
+
126
+
### Adding the Function
127
+
128
+
You can always add the function directly to the function dictionary, using the
129
+
_addFunction(String functionName, FunctionIfc function)_ method.
130
+
131
+
But there is an easier way by using the expression configuration, which also allows you to add more
132
+
than one function:
133
+
134
+
```java
135
+
ExpressionConfiguration configuration =
136
+
ExpressionConfiguration.defaultConfiguration()
137
+
.withAdditionalFunctions(
138
+
Map.entry("MAX_VALUE", new MaxFunction()),
139
+
Map.entry("MIN_VALUE", new MinFunction()));
140
+
141
+
Expression expression = new Expression("MAX_VALUE(1,2,3) + MIN_VALUE(7,8,9)");
142
+
```
+86
docs/customization/custom_operators.md
+86
docs/customization/custom_operators.md
···
1
+
---
2
+
layout: default
3
+
title: Custom Operators
4
+
parent: Customization
5
+
nav_order: 2
6
+
---
7
+
8
+
## Custom Operators
9
+
10
+
EvalEx comes with a predefined set of ready-to use operators.
11
+
A standard collection is added to the default configuration, using the
12
+
_ExpressionConfiguration.StandardOperatorsDictionary_.
13
+
14
+
Operation definition is made in two parts:
15
+
16
+
- Defining the operator type and logic by adding a class that implements the _OperatorIfc_
17
+
interface.
18
+
- Adding the operator with a unique name to the operator dictionary, so it can be found and used in
19
+
expressions.
20
+
21
+
### Defining the Operator
22
+
23
+
To ease some common implementation routines, a custom operator usually extends the
24
+
_AbstractOperator_ class.
25
+
As an example, we can look at the boolean "AND" operator:
26
+
27
+
```java
28
+
@InfixOperator(precedence = OPERATOR_PRECEDENCE_AND)
29
+
public class InfixAndOperator extends AbstractOperator {
30
+
31
+
@Override
32
+
public EvaluationValue evaluate(
33
+
Expression expression, Token operatorToken, EvaluationValue... operands) {
34
+
return new EvaluationValue(operands[0].getBooleanValue() && operands[1].getBooleanValue());
35
+
}
36
+
}
37
+
```
38
+
39
+
As you can see, the only method that has to be implemented, is the _evaluate()_ method. It will be
40
+
called by EvalEx during evaluation. It will pass the operand values to the method.
41
+
42
+
The expression itself and also the operator token are passed to the call, they can be used to
43
+
access the configuration or to find out the operator name and its position in the expression.
44
+
45
+
#### Operator type
46
+
47
+
The operator type is determined through the corresponding annotation:
48
+
49
+
- @PrefixOperator
50
+
- @InfixOperator
51
+
- @PostfixOperator
52
+
53
+
Infix operators will receive two operands in the _evaluate()_ method, pre- and postfix operator will
54
+
receive one operand.
55
+
56
+
#### Operator Precedence and associativity
57
+
58
+
The order of expression evaluation is determined by the operator precedence and its associativity.
59
+
60
+
The precedence tells EvalEx which operator have to be evaluated first. With this, it is e.g. defined
61
+
that multiplication happens before addition.
62
+
63
+
The associativity tells in which order (left to right, right to left) operators of the same
64
+
precedence are evaluated.
65
+
66
+
Precedence and associativity can be specified with the operator annotation.
67
+
68
+
There is a collection of predefined operator precedences in the _OperatorIfc_ interface.
69
+
70
+
### Adding the Operator
71
+
72
+
You can always add the operator directly to the operator dictionary, using the
73
+
_addOperator(String operatorString, OperatorIfc operator)_ method.
74
+
75
+
But there is an easier way by using the expression configuration, which also allows you to add more
76
+
than one operator:
77
+
78
+
```java
79
+
ExpressionConfiguration configuration =
80
+
ExpressionConfiguration.defaultConfiguration()
81
+
.withAdditionalOperators(
82
+
Map.entry("AND", new InfixAndOperator()),
83
+
Map.entry("OR", new InfixOrOperator()));
84
+
85
+
Expression expression = new Expression("(a > 5 AND x < 10) OR (y < 0)");
86
+
```
+6
docs/customization/customization.md
+6
docs/customization/customization.md
+70
docs/customization/data_access.md
+70
docs/customization/data_access.md
···
1
+
---
2
+
layout: default
3
+
title: Data Access
4
+
parent: Customization
5
+
nav_order: 5
6
+
---
7
+
8
+
## Data Access
9
+
10
+
By default, EvalEx has its own storage for variable values, the _MapBasedDataAccessor_.
11
+
It stores and retrieves the variables in a case-insensitive _TreeMap_.
12
+
A separate instance of the _MapBasedDataAccessor_ will be created through the default configured
13
+
supplier for each new Expression, separating the storage for each expression instance.
14
+
15
+
You can easily define your own data access interface, by defining a class that implements the
16
+
_DataAccessorInterface_:
17
+
18
+
```java
19
+
public interface DataAccessorIfc {
20
+
21
+
/**
22
+
* Retrieves a data value.
23
+
*
24
+
* @param variable The variable name, e.g. a variable or constant name.
25
+
* @return The data value, or <code>null</code> if not found.
26
+
*/
27
+
EvaluationValue getData(String variable);
28
+
29
+
/**
30
+
* Sets a data value.
31
+
*
32
+
* @param variable The variable name, e.g. a variable or constant name.
33
+
* @param value The value to set.
34
+
*/
35
+
void setData(String variable, EvaluationValue value);
36
+
}
37
+
```
38
+
39
+
Only two methods need to be implemented:
40
+
41
+
- getData(String variable) - should return a value for a variable name.
42
+
- setData(String variable, EvaluationValue value) - set a variable value.
43
+
44
+
The _setData()_ method is only called when a variable is added to the expression, e.g. using the
45
+
_with()_ method. You may leave the implementation empty, if you do want to support this.
46
+
47
+
> NOTE: The variable names that are passed to the methods are case-sensitive, just like they were
48
+
> entered in the expression.
49
+
50
+
The custom data accessor can then be specified in the expression configuration:
51
+
52
+
```java
53
+
ExpressionConfiguration configuration = ExpressionConfiguration.builder()
54
+
.dataAccessorSupplier(MyCustomDataAccessor::new)
55
+
.build();
56
+
57
+
Expression expression = new Expression("2.128 + a", configuration);
58
+
```
59
+
60
+
The example here creates a new instance of the _MyCustomDataAccessor_ for each new instance of an
61
+
expression. But you could also share an instance for all expressions:
62
+
63
+
```java
64
+
final MyCustomDataAccessor customAccessor = new MyCustomDataAccessor();
65
+
ExpressionConfiguration configuration = ExpressionConfiguration.builder()
66
+
.dataAccessorSupplier(() -> customAccessor)
67
+
.build();
68
+
69
+
Expression expression = new Expression("2.128 + a", configuration);
70
+
```
+65
docs/customization/function_dictionary.md
+65
docs/customization/function_dictionary.md
···
1
+
---
2
+
layout: default
3
+
title: Function Dictionary
4
+
parent: Customization
5
+
nav_order: 3
6
+
---
7
+
8
+
## Function Dictionary
9
+
10
+
By default, EvalEx has its own dictionary for the functions that can be used in an expression,
11
+
the _MapBasedFunctionDictionary_. It stores and retrieves the functions in a case-insensitive
12
+
_TreeMap_.
13
+
14
+
You can easily define your own function directory, by defining a class that implements the
15
+
_FunctionDictionaryIfc_:
16
+
17
+
```java
18
+
public interface FunctionDictionaryIfc {
19
+
20
+
/**
21
+
* Allows to add a function to the dictionary. Implementation is optional, if you have a fixed set
22
+
* of functions, this method can throw an exception.
23
+
*
24
+
* @param functionName The function name.
25
+
* @param function The function implementation.
26
+
*/
27
+
void addFunction(String functionName, FunctionIfc function);
28
+
29
+
/**
30
+
* Check if the dictionary has a function with that name.
31
+
*
32
+
* @param functionName The function name to look for.
33
+
* @return <code>true</code> if a function was found or <code>false</code> if not.
34
+
*/
35
+
default boolean hasFunction(String functionName) {
36
+
return getFunction(functionName) != null;
37
+
}
38
+
39
+
/**
40
+
* Get the function definition for a function name.
41
+
*
42
+
* @param functionName The name of the function.
43
+
* @return The function definition or <code>null</code> if no function was found.
44
+
*/
45
+
FunctionIfc getFunction(String functionName);
46
+
```
47
+
48
+
Only two methods need to be implemented:
49
+
50
+
- addFunction(String functionName, FunctionIfc function) - which allows to add a function.
51
+
- FunctionIfc getFunction(String functionName) - retrieves a function implementation.
52
+
53
+
The _addFunction()_ method is only called when a function is added to the configuration, using the
54
+
_withAdditionalFunctions()_ in the _ExpressionConfiguration_ object.
55
+
56
+
> NOTE: The function names that are passed to the methods are case-sensitive, just like they were
57
+
> entered in the expression.
58
+
59
+
The custom function dictionary can then be specified in the expression configuration:
60
+
61
+
```java
62
+
ExpressionConfiguration configuration = ExpressionConfiguration.builder()
63
+
.functionDictionary(new MyFunctionDirectory())
64
+
.build();
65
+
```
+104
docs/customization/operator_dictionary.md
+104
docs/customization/operator_dictionary.md
···
1
+
---
2
+
layout: default
3
+
title: Operator Dictionary
4
+
parent: Customization
5
+
nav_order: 4
6
+
---
7
+
8
+
## Function Dictionary
9
+
10
+
By default, EvalEx has its own dictionary for the operators that can be used in an expression,
11
+
the _MapBasedOperatorDictionary_. It stores and retrieves the operators in a case-insensitive
12
+
_TreeMap_.
13
+
14
+
You can easily define your own operator directory, by defining a class that implements the
15
+
_FunctionDictionaryIfc_:
16
+
17
+
```java
18
+
public interface OperatorDictionaryIfc {
19
+
20
+
/**
21
+
* Allows to add an operator to the dictionary. Implementation is optional, if you have a fixed
22
+
* set of operators, this method can throw an exception.
23
+
*
24
+
* @param operatorString The operator name.
25
+
* @param operator The operator implementation.
26
+
*/
27
+
void addOperator(String operatorString, OperatorIfc operator);
28
+
29
+
/**
30
+
* Check if the dictionary has a prefix operator with that name.
31
+
*
32
+
* @param operatorString The operator name to look for.
33
+
* @return <code>true</code> if an operator was found or <code>false</code> if not.
34
+
*/
35
+
default boolean hasPrefixOperator(String operatorString) {
36
+
return getPrefixOperator(operatorString) != null;
37
+
}
38
+
39
+
/**
40
+
* Check if the dictionary has a postfix operator with that name.
41
+
*
42
+
* @param operatorString The operator name to look for.
43
+
* @return <code>true</code> if an operator was found or <code>false</code> if not.
44
+
*/
45
+
default boolean hasPostfixOperator(String operatorString) {
46
+
return getPostfixOperator(operatorString) != null;
47
+
}
48
+
49
+
/**
50
+
* Check if the dictionary has an infix operator with that name.
51
+
*
52
+
* @param operatorString The operator name to look for.
53
+
* @return <code>true</code> if an operator was found or <code>false</code> if not.
54
+
*/
55
+
default boolean hasInfixOperator(String operatorString) {
56
+
return getInfixOperator(operatorString) != null;
57
+
}
58
+
59
+
/**
60
+
* Get the operator definition for a prefix operator name.
61
+
*
62
+
* @param operatorString The name of the operator.
63
+
* @return The operator definition or <code>null</code> if no operator was found.
64
+
*/
65
+
OperatorIfc getPrefixOperator(String operatorString);
66
+
67
+
/**
68
+
* Get the operator definition for a postfix operator name.
69
+
*
70
+
* @param operatorString The name of the operator.
71
+
* @return The operator definition or <code>null</code> if no operator was found.
72
+
*/
73
+
OperatorIfc getPostfixOperator(String operatorString);
74
+
75
+
/**
76
+
* Get the operator definition for an infix operator name.
77
+
*
78
+
* @param operatorString The name of the operator.
79
+
* @return The operator definition or <code>null</code> if no operator was found.
80
+
*/
81
+
OperatorIfc getInfixOperator(String operatorString);
82
+
}
83
+
```
84
+
85
+
Only four methods need to be implemented:
86
+
87
+
- addOperator(String operatorString, OperatorIfc operator) - which allows to add an operator.
88
+
- OperatorIfc getPrefixOperator(String operatorString) - retrieves a prefix operator.
89
+
- OperatorIfc getPostfixOperator(String operatorString) - retrieves a postfix operator.
90
+
- OperatorIfc getInfixOperator(String operatorString) - retrieves a infix operator.
91
+
92
+
The _addOperator()_ method is only called when an operator is added to the configuration, using the
93
+
_withAdditionalOperators()_ in the _ExpressionConfiguration_ object.
94
+
95
+
> NOTE: The operator names that are passed to the methods are case-sensitive, just like they were
96
+
> entered in the expression.
97
+
98
+
The custom operator dictionary can then be specified in the expression configuration:
99
+
100
+
```java
101
+
ExpressionConfiguration configuration = ExpressionConfiguration.builder()
102
+
.operatorDictionary(new MyOperatorDictionary())
103
+
.build();
104
+
```
+131
docs/index.md
+131
docs/index.md
···
1
+
---
2
+
layout: default
3
+
title: Welcome
4
+
nav_order: 1
5
+
---
6
+
7
+
# EvalEx - Java Expression Evaluator
8
+
9
+
EvalEx is a handy expression evaluator for Java, that allows to parse and evaluate expression
10
+
strings.
11
+
12
+
## Key Features:
13
+
14
+
- Supports numerical, boolean, string, array and structure expressions, operations and variables.
15
+
- Array and structure support: Arrays and structures can be mixed, building arbitrary data
16
+
structures.
17
+
- Uses BigDecimal for numerical calculations.
18
+
- MathContext and number of decimal places can be configured, with optional automatic rounding.
19
+
- No dependencies to external libraries.
20
+
- Easy integration into existing systems to access data.
21
+
- Predefined boolean and mathematical operators.
22
+
- Predefined mathematical, boolean and string functions.
23
+
- Custom functions and operators can be added.
24
+
- Functions can be defined with a variable number of arguments (see MIN, MAX and SUM functions).
25
+
- Supports hexadecimal and scientific notations of numbers.
26
+
- Supports implicit multiplication, e.g. (a+b)(a-b) or 2(x-y) which equals to (a+b)\*(a-b) or 2\*(
27
+
x-y)
28
+
- Lazy evaluation of function parameters (see the IF function) and support of sub-expressions.
29
+
30
+
## Examples
31
+
32
+
A simple example, that shows how it works in general:
33
+
34
+
```java
35
+
Expression expression = new Expression("1 + 2 / (4 * SQRT(4))");
36
+
37
+
EvaluationValue result = expression.evaluate();
38
+
39
+
System.out.println(result.getNumberValue()); // prints 1.25
40
+
```
41
+
42
+
Of course, variables can be specified in the expression and their values can be passed for
43
+
evaluation:
44
+
45
+
```java
46
+
Expression expression = new Expression("(a + b) * (a - b)");
47
+
48
+
EvaluationValue result = expression
49
+
.with("a", 3.5)
50
+
.and("b", 2.5)
51
+
.evaluate();
52
+
53
+
System.out.println(result.getNumberValue()); // prints 6.00
54
+
```
55
+
56
+
Boolean expressions produce a boolean result:
57
+
58
+
```java
59
+
Expression expression = new Expression("level > 2 || level <= 0");
60
+
61
+
EvaluationValue result = expression
62
+
.with("level", 3.5)
63
+
.evaluate();
64
+
65
+
System.out.println(result.getBooleanValue()); // prints true
66
+
```
67
+
68
+
Like in Java, strings and text can be mixed:
69
+
70
+
```java
71
+
Expression expression = new Expression("\"Hello \" + name + \", you are \" + age")
72
+
.with("name","Frank")
73
+
.and("age",38);
74
+
75
+
System.out.println(expression.evaluate().getStringValue()); // prints Hello Frank, you are 38
76
+
```
77
+
78
+
Arrays are supported and can be passed as Java _Lists_:
79
+
80
+
```java
81
+
Expression expression = new Expression("values[i-1] * factors[i-1]");
82
+
83
+
EvaluationValue result = expression
84
+
.with("values", List.of(2, 3, 4))
85
+
.and("factors", List.of(2, 4, 6))
86
+
.and("i", 1)
87
+
.evaluate();
88
+
89
+
System.out.println(result.getNumberValue()); // prints 4
90
+
```
91
+
92
+
Structures are supported and can be passed as Java _Maps_.
93
+
Arrays and Structures can be combined to build arbitrary data structures:
94
+
95
+
```java
96
+
Map<String, Object> order = new HashMap<>();
97
+
order.put("id", 12345);
98
+
order.put("name", "Mary");
99
+
100
+
Map<String, Object> position = new HashMap<>();
101
+
position.put("article", 3114);
102
+
position.put("amount", 3);
103
+
position.put("price", new BigDecimal("14.95"));
104
+
105
+
order.put("positions", List.of(position));
106
+
107
+
Expression expression = new Expression("order.positions[x].amount * order.positions[x].price")
108
+
.with("order", order)
109
+
.and("x", 0);
110
+
111
+
BigDecimal result = expression.evaluate().getNumberValue();
112
+
113
+
System.out.println(result); // prints 44.85
114
+
```
115
+
116
+
## Author and License
117
+
118
+
Copyright 2012-2022 by Udo Klimaschewski
119
+
120
+
**Thanks to all who contributed to this
121
+
project: [Contributors](https://github.com/ezylang/EvalEx/graphs/contributors)**
122
+
123
+
The software is licensed under the Apache License, Version 2.0 (
124
+
see [LICENSE](https://raw.githubusercontent.com/ezylang/EvalEx/main/LICENSE) file).
125
+
126
+
* The *power of* operator (^) implementation was copied
127
+
from [Stack Overflow](http://stackoverflow.com/questions/3579779/how-to-do-a-fractional-power-on-bigdecimal-in-java)
128
+
Thanks to Gene Marin
129
+
* The SQRT() function implementation was taken from the
130
+
book [The Java Programmers Guide To numerical Computing](http://www.amazon.de/Java-Number-Cruncher-Programmers-Numerical/dp/0130460419) (
131
+
Ronald Mak, 2002)
+19
docs/references/constants.md
+19
docs/references/constants.md
···
1
+
---
2
+
layout: default
3
+
title: Standard Constants
4
+
nav_order: 1
5
+
parent: References
6
+
---
7
+
8
+
### Standard Constants
9
+
10
+
Available through the _ExpressionConfiguration.StandardConstants_ constant:
11
+
12
+
| Name | Value |
13
+
|-------|---------------------------------------------------------------|
14
+
| TRUE | Boolean.TRUE |
15
+
| FALSE | Boolean.FALSE |
16
+
| PI | 3.14159265358979323846264338327950288419716939937510582097... |
17
+
| E | 2.71828182845904523536028747135266249775724709369995957496... |
18
+
19
+
+76
docs/references/functions.md
+76
docs/references/functions.md
···
1
+
---
2
+
layout: default
3
+
title: Standard Functions
4
+
nav_order: 3
5
+
parent: References
6
+
---
7
+
8
+
## Standard Functions
9
+
10
+
Available through the _ExpressionConfiguration.StandardFunctionsDictionary_ constant:
11
+
12
+
### Basic Functions
13
+
14
+
| Name | Value |
15
+
|--------------------------------------------|-------------------------------------------------------------------------------------------------------------------------|
16
+
| ABS(value) | Absolute (non-negative) value |
17
+
| CEILING(value) | Rounds the given value an integer using the rounding mode CEILING |
18
+
| FACT(base) | Calculates the factorial of a base value |
19
+
| FLOOR(value) | Rounds the given value an integer using the rounding mode FLOOR |
20
+
| IF(condition, resultIfTrue, resultIfFalse) | Conditional evaluation function. If _condition_ is true, the _resultIfTrue_ is returned, else the _resultIfFalse_ value |
21
+
| LOG(value) | The natural logarithm (base e) of a value |
22
+
| LOG10(value) | The base 10 logarithm of a value |
23
+
| MAX(value, ...) | Returns the maximum value of all parameters |
24
+
| MIN(value, ...) | Returns the maximum value of all parameters |
25
+
| NOT(value) | Boolean negation, implemented as a function (for compatibility) |
26
+
| RANDOM() | Produces a random value between 0 and 1 |
27
+
| ROUND(value, scale) | Rounds the given value to the specified scale, using the current rounding mode |
28
+
| SQRT(value) | Square root function |
29
+
| SUM(value, ...) | Returns the sum of all parameters |
30
+
31
+
### String Functions
32
+
33
+
| Name | Value |
34
+
|---------------------------------|-----------------------------------------------------------------------|
35
+
| STR_CONTAINS(string, substring) | Returns true, if the string contains the substring (case-insensitive) |
36
+
| STR_LOWER(value) | Converts the given value to lower case |
37
+
| STR_UPPER(value) | Converts the given value to upper case |
38
+
39
+
### trigonometric Functions
40
+
41
+
| Name | Value |
42
+
|--------------|------------------------------------------------------------------------------------------------|
43
+
| ACOS(value) | Returns the the arc-cosine (in degrees) |
44
+
| ACOSH(value) | Returns the the hyperbolic arc-cosine (in degrees) |
45
+
| ACOSR(value) | Returns the the arc-cosine (in radians) |
46
+
| ACOT(value) | Returns the the arc-co-tangent (in degrees) |
47
+
| ACOTR(value) | Returns the the arc-co-tangent (in radians) |
48
+
| ASIN(value) | Returns the the arc-sine (in degrees) |
49
+
| ASINH(value) | Returns the the hyperbolic arc-sine (in degrees) |
50
+
| ASINR(value) | Returns the the arc-sine (in radians) |
51
+
| ATAN(value) | Returns the the arc-tangent (in degrees) |
52
+
| ATAN(value) | Returns the the arc-tangent (in degrees) |
53
+
| ATAN2(y, x) | Returns the angle of atan2 (in degrees) |
54
+
| ATAN2R(y, x) | Returns the angle of atan2 (in radians) |
55
+
| ATANR(value) | Returns the the arc-tangent (in radians) |
56
+
| COS(value) | Returns the cosine of an angle (in degrees) |
57
+
| COSH(value) | Returns the hyperbolic cosine of a value |
58
+
| COSR(value) | Returns the cosine of an angle (in radians) |
59
+
| COT(value) | Returns the co-tangent of an angle (in degrees) |
60
+
| COTH(value) | Returns the hyperbolic co-tangent of a value |
61
+
| COTR(value) | Returns the co-tangent of an angle (in radians) |
62
+
| CSC(value) | Returns the co-secant of an angle (in degrees) |
63
+
| CSCH(value) | Returns the hyperbolic co-secant of a value |
64
+
| CSCR(value) | Returns the co-secant of an angle (in radians) |
65
+
| DEG(rad) | Converts an angle measured in radians to an approximately equivalent angle measured in degrees |
66
+
| RAD(degrees) | Converts an angle measured in degrees to an approximately equivalent angle measured in radians |
67
+
| SEC(value) | Returns the secant of an angle (in degrees) |
68
+
| SECH(value) | Returns the hyperbolic secant of an angle (in degrees) |
69
+
| SECR(value) | Returns the secant of an angle (in radians) |
70
+
| SIN(value) | Returns the sine of an angle (in degrees) |
71
+
| SINH(value) | Returns the hyperbolic sine of a value |
72
+
| SINR(value) | Returns the sine of an angle (in radians) |
73
+
| TAN(value) | Returns the tangent of an angle (in degrees) |
74
+
| TANH(value) | Returns the hyperbolic tangent of a value |
75
+
| TANR(value) | Returns the tangent of an angle (in radians) |
76
+
+38
docs/references/operators.md
+38
docs/references/operators.md
···
1
+
---
2
+
layout: default
3
+
title: Standard Operators
4
+
nav_order: 2
5
+
parent: References
6
+
---
7
+
8
+
## Standard Operators
9
+
10
+
Available through the _ExpressionConfiguration.StandardOperatorsDictionary_ constant:
11
+
12
+
### Arithmetic operators
13
+
14
+
| Name | Value |
15
+
|------|-----------------------------------------|
16
+
| - | The prefix minus operator, like in "-2" |
17
+
| + | The prefix minus operator, like in "+2" |
18
+
| - | The infix minus operator, like in "5-2" |
19
+
| + | The infix plus operator, like in "5+2" |
20
+
| * | The multiplication operator |
21
+
| / | The division operator |
22
+
| ^ | The power-of operator |
23
+
| % | The modulo operator (remainder) |
24
+
25
+
### Boolean operators
26
+
27
+
| Name | Value |
28
+
|--------------|-------------------------------------|
29
+
| =, == | The equals operator |
30
+
| !=, <> | The not equals operator |
31
+
| ! | The prefix not operator, like in !a |
32
+
| > | The greater than operator |
33
+
| >= | The greater equals operator |
34
+
| < | The less than operator |
35
+
| <= | The less equals operator |
36
+
| && | The and operator |
37
+
| || | The or operator |
38
+
+6
docs/references/references.md
+6
docs/references/references.md
+99
-146
pom.xml
+99
-146
pom.xml
···
3
3
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4
4
<modelVersion>4.0.0</modelVersion>
5
5
6
-
<groupId>com.udojava</groupId>
6
+
<groupId>com.ezylang</groupId>
7
7
<artifactId>EvalEx</artifactId>
8
-
<version>2.8-SNAPSHOT</version>
8
+
<version>3.0.0-SNAPSHOT</version>
9
9
10
-
<name>EvalEx</name>
11
-
<description>EvalEx is a handy expression evaluator for Java, that allows to evaluate simple
12
-
mathematical and boolean expressions.
10
+
<description>EvalEx is a handy expression evaluator for Java, that allows to evaluate
11
+
expressions.
13
12
</description>
14
-
<url>https://github.com/uklimaschewski/EvalEx</url>
13
+
<url>https://github.com/ezylang/EvalEx</url>
15
14
16
15
<organization>
17
16
<name>Udo Klimaschewski</name>
18
-
<url>http://www.udojava.com</url>
17
+
<url>https://github.com/ezylang/EvalEx</url>
19
18
</organization>
20
19
21
20
<licenses>
22
21
<license>
23
-
<name>MIT License</name>
24
-
<url>http://www.opensource.org/licenses/mit-license.php</url>
22
+
<name>Apache License 2.0</name>
23
+
<url>https://github.com/ezylang/EvalEx/blob/main/LICENSE</url>
25
24
</license>
26
25
</licenses>
27
26
···
33
32
</developers>
34
33
35
34
<scm>
36
-
<connection>scm:git:git@github.com:uklimaschewski/EvalEx.git</connection>
37
-
<developerConnection>scm:git:git@github.com:uklimaschewski/EvalEx.git</developerConnection>
38
-
<url>https://github.com/uklimaschewski/EvalEx</url>
35
+
<connection>scm:git:git@github.com:ezylang/EvalEx.git</connection>
36
+
<developerConnection>scm:git:git@github.com:ezylang/EvalEx.git</developerConnection>
37
+
<url>https://github.com/ezylang/EvalEx</url>
39
38
</scm>
40
39
41
40
<issueManagement>
42
41
<system>github</system>
43
-
<url>https://github.com/uklimaschewski/EvalEx/issues</url>
42
+
<url>https://github.com/ezylang/EvalEx/issues</url>
44
43
</issueManagement>
45
44
46
-
<distributionManagement>
47
-
<repository>
48
-
<id>ossrh</id>
49
-
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
50
-
</repository>
51
-
<snapshotRepository>
52
-
<id>ossrh</id>
53
-
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
54
-
</snapshotRepository>
55
-
</distributionManagement>
56
-
57
45
<properties>
58
46
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
47
+
<version.junit>5.9.0</version.junit>
48
+
<maven.compiler.source>11</maven.compiler.source>
49
+
<maven.compiler.target>11</maven.compiler.target>
59
50
</properties>
60
51
61
52
<dependencies>
53
+
<!-- We do not allow any runtime dependencies in EvalEx -->
54
+
<!-- Project Lombok needs no runtime dependency, hence it has scope "provided" -->
62
55
<dependency>
63
-
<groupId>junit</groupId>
64
-
<artifactId>junit</artifactId>
65
-
<version>4.13.1</version>
56
+
<groupId>org.projectlombok</groupId>
57
+
<artifactId>lombok</artifactId>
58
+
<version>1.18.24</version>
59
+
<scope>provided</scope>
60
+
</dependency>
61
+
<!-- Dependencies used for testing -->
62
+
<dependency>
63
+
<groupId>org.junit.jupiter</groupId>
64
+
<artifactId>junit-jupiter-engine</artifactId>
65
+
<version>${version.junit}</version>
66
+
<scope>test</scope>
67
+
</dependency>
68
+
<dependency>
69
+
<groupId>org.junit.jupiter</groupId>
70
+
<artifactId>junit-jupiter-params</artifactId>
71
+
<version>${version.junit}</version>
72
+
<scope>test</scope>
73
+
</dependency>
74
+
<dependency>
75
+
<groupId>org.assertj</groupId>
76
+
<artifactId>assertj-core</artifactId>
77
+
<version>3.23.1</version>
78
+
<scope>test</scope>
79
+
</dependency>
80
+
<dependency>
81
+
<groupId>org.mockito</groupId>
82
+
<artifactId>mockito-core</artifactId>
83
+
<version>4.6.1</version>
66
84
<scope>test</scope>
67
85
</dependency>
68
86
</dependencies>
···
70
88
<build>
71
89
<plugins>
72
90
<plugin>
91
+
<groupId>com.diffplug.spotless</groupId>
92
+
<artifactId>spotless-maven-plugin</artifactId>
93
+
<version>2.23.0</version>
94
+
<executions>
95
+
<execution>
96
+
<phase>validate</phase>
97
+
<goals>
98
+
<goal>check</goal>
99
+
</goals>
100
+
</execution>
101
+
</executions>
102
+
<configuration>
103
+
<java>
104
+
<includes>
105
+
<include>src/main/java/**/*.java</include>
106
+
<include>src/test/java/**/*.java</include>
107
+
</includes>
108
+
<googleJavaFormat>
109
+
<version>1.15.0</version>
110
+
<style>GOOGLE</style>
111
+
<reflowLongStrings>true</reflowLongStrings>
112
+
</googleJavaFormat>
113
+
<licenseHeader>
114
+
<file>${basedir}/spotless/header.txt</file>
115
+
</licenseHeader>
116
+
</java>
117
+
</configuration>
118
+
</plugin>
119
+
<plugin>
73
120
<groupId>org.apache.maven.plugins</groupId>
74
121
<artifactId>maven-compiler-plugin</artifactId>
75
-
<version>3.8.1</version>
76
-
<configuration>
77
-
<source>1.6</source>
78
-
<target>1.6</target>
79
-
</configuration>
122
+
<version>3.8.0</version>
80
123
</plugin>
81
124
<plugin>
82
-
<groupId>org.moditect</groupId>
83
-
<artifactId>moditect-maven-plugin</artifactId>
84
-
<version>1.0.0.RC2</version>
125
+
<groupId>org.apache.maven.plugins</groupId>
126
+
<artifactId>maven-surefire-plugin</artifactId>
127
+
<version>2.22.2</version>
128
+
<dependencies>
129
+
<dependency>
130
+
<groupId>org.junit.platform</groupId>
131
+
<artifactId>junit-platform-surefire-provider</artifactId>
132
+
<version>1.2.0</version>
133
+
</dependency>
134
+
</dependencies>
135
+
</plugin>
136
+
<plugin>
137
+
<groupId>org.jacoco</groupId>
138
+
<artifactId>jacoco-maven-plugin</artifactId>
139
+
<version>0.8.5</version>
85
140
<executions>
86
141
<execution>
87
-
<id>add-module-info</id>
88
-
<phase>package</phase>
142
+
<id>prepare-agent</id>
89
143
<goals>
90
-
<goal>add-module-info</goal>
144
+
<goal>prepare-agent</goal>
91
145
</goals>
92
-
<configuration>
93
-
<jvmVersion>9</jvmVersion>
94
-
<overwriteExistingFiles>true</overwriteExistingFiles>
95
-
<module>
96
-
<moduleInfoFile>
97
-
${project.basedir}/src/main/java9/module-info.java
98
-
</moduleInfoFile>
99
-
</module>
100
-
</configuration>
146
+
</execution>
147
+
<execution>
148
+
<id>report</id>
149
+
<phase>prepare-package</phase>
150
+
<goals>
151
+
<goal>report</goal>
152
+
</goals>
101
153
</execution>
102
154
</executions>
103
155
</plugin>
104
156
</plugins>
105
157
</build>
106
-
107
-
<profiles>
108
-
<profile>
109
-
<id>coverage</id>
110
-
<build>
111
-
<plugins>
112
-
<plugin>
113
-
<groupId>org.jacoco</groupId>
114
-
<artifactId>jacoco-maven-plugin</artifactId>
115
-
<version>0.8.5</version>
116
-
<executions>
117
-
<execution>
118
-
<id>prepare-agent</id>
119
-
<goals>
120
-
<goal>prepare-agent</goal>
121
-
</goals>
122
-
</execution>
123
-
<execution>
124
-
<id>report</id>
125
-
<phase>test</phase>
126
-
<goals>
127
-
<goal>report</goal>
128
-
</goals>
129
-
</execution>
130
-
</executions>
131
-
</plugin>
132
-
</plugins>
133
-
</build>
134
-
</profile>
135
-
136
-
<profile>
137
-
<id>publish</id>
138
-
<build>
139
-
<plugins>
140
-
141
-
<plugin>
142
-
<artifactId>maven-source-plugin</artifactId>
143
-
<version>3.0.1</version>
144
-
<executions>
145
-
<execution>
146
-
<id>attach-sources</id>
147
-
<phase>package</phase>
148
-
<goals>
149
-
<goal>jar-no-fork</goal>
150
-
</goals>
151
-
</execution>
152
-
</executions>
153
-
</plugin>
154
-
155
-
<plugin>
156
-
<artifactId>maven-javadoc-plugin</artifactId>
157
-
<version>3.0.0-M1</version>
158
-
<configuration>
159
-
<doclint>none</doclint>
160
-
<detectJavaApiLink>false</detectJavaApiLink>
161
-
</configuration>
162
-
<executions>
163
-
<execution>
164
-
<id>attach-javadocs</id>
165
-
<phase>package</phase>
166
-
<goals>
167
-
<goal>jar</goal>
168
-
</goals>
169
-
</execution>
170
-
</executions>
171
-
</plugin>
172
-
173
-
<plugin>
174
-
<groupId>org.apache.maven.plugins</groupId>
175
-
<artifactId>maven-gpg-plugin</artifactId>
176
-
<version>1.6</version>
177
-
<executions>
178
-
<execution>
179
-
<id>sign-artifacts</id>
180
-
<phase>verify</phase>
181
-
<goals>
182
-
<goal>sign</goal>
183
-
</goals>
184
-
</execution>
185
-
</executions>
186
-
</plugin>
187
-
188
-
<plugin>
189
-
<groupId>org.sonatype.plugins</groupId>
190
-
<artifactId>nexus-staging-maven-plugin</artifactId>
191
-
<version>1.6.8</version>
192
-
<extensions>true</extensions>
193
-
<configuration>
194
-
<serverId>ossrh</serverId>
195
-
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
196
-
<autoReleaseAfterClose>true</autoReleaseAfterClose>
197
-
</configuration>
198
-
</plugin>
199
-
200
-
</plugins>
201
-
</build>
202
-
</profile>
203
-
</profiles>
204
-
205
158
</project>
+15
spotless/header.txt
+15
spotless/header.txt
···
1
+
/*
2
+
Copyright 2012-$YEAR Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
+39
src/main/java/com/ezylang/evalex/BaseException.java
+39
src/main/java/com/ezylang/evalex/BaseException.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex;
17
+
18
+
import lombok.EqualsAndHashCode;
19
+
import lombok.Getter;
20
+
import lombok.ToString;
21
+
22
+
/** Base exception class used in EvalEx. */
23
+
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
24
+
@ToString
25
+
public class BaseException extends Exception {
26
+
27
+
@Getter @EqualsAndHashCode.Include private final int startPosition;
28
+
@Getter @EqualsAndHashCode.Include private final int endPosition;
29
+
@Getter @EqualsAndHashCode.Include private final String tokenString;
30
+
@Getter @EqualsAndHashCode.Include private final String message;
31
+
32
+
public BaseException(int startPosition, int endPosition, String tokenString, String message) {
33
+
super(message);
34
+
this.startPosition = startPosition;
35
+
this.endPosition = endPosition;
36
+
this.tokenString = tokenString;
37
+
this.message = super.getMessage();
38
+
}
39
+
}
+34
src/main/java/com/ezylang/evalex/EvaluationException.java
+34
src/main/java/com/ezylang/evalex/EvaluationException.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex;
17
+
18
+
import com.ezylang.evalex.parser.Token;
19
+
20
+
/** Exception while evaluating the parsed expression. */
21
+
public class EvaluationException extends BaseException {
22
+
23
+
public EvaluationException(Token token, String message) {
24
+
super(
25
+
token.getStartPosition(),
26
+
token.getStartPosition() + token.getValue().length(),
27
+
token.getValue(),
28
+
message);
29
+
}
30
+
31
+
public static EvaluationException ofUnsupportedDataTypeInOperation(Token token) {
32
+
return new EvaluationException(token, "Unsupported data types in operation");
33
+
}
34
+
}
+342
src/main/java/com/ezylang/evalex/Expression.java
+342
src/main/java/com/ezylang/evalex/Expression.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex;
17
+
18
+
import com.ezylang.evalex.config.ExpressionConfiguration;
19
+
import com.ezylang.evalex.data.DataAccessorIfc;
20
+
import com.ezylang.evalex.data.EvaluationValue;
21
+
import com.ezylang.evalex.functions.FunctionIfc;
22
+
import com.ezylang.evalex.parser.ASTNode;
23
+
import com.ezylang.evalex.parser.ParseException;
24
+
import com.ezylang.evalex.parser.ShuntingYardConverter;
25
+
import com.ezylang.evalex.parser.Token;
26
+
import com.ezylang.evalex.parser.Tokenizer;
27
+
import java.math.BigDecimal;
28
+
import java.util.ArrayList;
29
+
import java.util.List;
30
+
import java.util.Map;
31
+
import java.util.Set;
32
+
import java.util.TreeSet;
33
+
import lombok.Getter;
34
+
35
+
/**
36
+
* Main class that allow creating, parsing, passing parameters and evaluating an expression string.
37
+
*
38
+
* @see <a href="https://github.com/ezylang/EvalEx">EvalEx Homepage</a>
39
+
*/
40
+
public class Expression {
41
+
@Getter private final ExpressionConfiguration configuration;
42
+
43
+
@Getter private final String expressionString;
44
+
45
+
@Getter private final DataAccessorIfc dataAccessor;
46
+
47
+
private ASTNode abstractSyntaxTree;
48
+
49
+
/**
50
+
* Creates a new expression with the default configuration. The expression is not parsed until it
51
+
* is first evaluated or validated.
52
+
*
53
+
* @param expressionString A string holding an expression.
54
+
*/
55
+
public Expression(String expressionString) {
56
+
this(expressionString, ExpressionConfiguration.defaultConfiguration());
57
+
}
58
+
59
+
/**
60
+
* Creates a new expression with a custom configuration. The expression is not parsed until it is
61
+
* first evaluated or validated.
62
+
*
63
+
* @param expressionString A string holding an expression.
64
+
*/
65
+
public Expression(String expressionString, ExpressionConfiguration configuration) {
66
+
this.expressionString = expressionString;
67
+
this.configuration = configuration;
68
+
this.dataAccessor = configuration.getDataAccessorSupplier().get();
69
+
70
+
// add default constants to data
71
+
for (Map.Entry<String, EvaluationValue> constant :
72
+
configuration.getDefaultConstants().entrySet()) {
73
+
getDataAccessor().setData(constant.getKey(), constant.getValue());
74
+
}
75
+
}
76
+
77
+
/**
78
+
* Evaluates the expression by parsing it (if not done before) and the evaluating it.
79
+
*
80
+
* @return The evaluation result value.
81
+
* @throws EvaluationException If there were problems while evaluating the expression.
82
+
* @throws ParseException If there were problems while parsing the expression.
83
+
*/
84
+
public EvaluationValue evaluate() throws EvaluationException, ParseException {
85
+
return evaluateSubtree(getAbstractSyntaxTree());
86
+
}
87
+
88
+
/**
89
+
* Evaluates only a subtree of the abstract syntax tree.
90
+
*
91
+
* @param startNode The {@link ASTNode} to start evaluation from.
92
+
* @return The evaluation result value.
93
+
* @throws EvaluationException If there were problems while evaluating the expression.
94
+
*/
95
+
public EvaluationValue evaluateSubtree(ASTNode startNode) throws EvaluationException {
96
+
Token token = startNode.getToken();
97
+
EvaluationValue result;
98
+
switch (token.getType()) {
99
+
case NUMBER_LITERAL:
100
+
result = EvaluationValue.numberOfString(token.getValue(), configuration.getMathContext());
101
+
break;
102
+
case STRING_LITERAL:
103
+
result = new EvaluationValue(token.getValue());
104
+
break;
105
+
case VARIABLE_OR_CONSTANT:
106
+
result = getDataAccessor().getData(token.getValue());
107
+
if (result.isExpressionNode()) {
108
+
result = evaluateSubtree(result.getExpressionNode());
109
+
}
110
+
break;
111
+
case PREFIX_OPERATOR:
112
+
case POSTFIX_OPERATOR:
113
+
result =
114
+
token
115
+
.getOperatorDefinition()
116
+
.evaluate(this, token, evaluateSubtree(startNode.getParameters().get(0)));
117
+
break;
118
+
case INFIX_OPERATOR:
119
+
result =
120
+
token
121
+
.getOperatorDefinition()
122
+
.evaluate(
123
+
this,
124
+
token,
125
+
evaluateSubtree(startNode.getParameters().get(0)),
126
+
evaluateSubtree(startNode.getParameters().get(1)));
127
+
break;
128
+
case ARRAY_INDEX:
129
+
result = evaluateArrayIndex(startNode);
130
+
break;
131
+
case STRUCTURE_SEPARATOR:
132
+
result = evaluateStructureSeparator(startNode);
133
+
break;
134
+
case FUNCTION:
135
+
result = evaluateFunction(startNode, token);
136
+
break;
137
+
default:
138
+
throw new EvaluationException(token, "Unexpected evaluation token: " + token);
139
+
}
140
+
141
+
return result.isNumberValue() ? roundAndStripZerosIfNeeded(result) : result;
142
+
}
143
+
144
+
private EvaluationValue evaluateFunction(ASTNode startNode, Token token)
145
+
throws EvaluationException {
146
+
List<EvaluationValue> parameterResults = new ArrayList<>();
147
+
for (int i = 0; i < startNode.getParameters().size(); i++) {
148
+
if (token.getFunctionDefinition().isParameterLazy(i)) {
149
+
parameterResults.add(new EvaluationValue(startNode.getParameters().get(i)));
150
+
} else {
151
+
parameterResults.add(evaluateSubtree(startNode.getParameters().get(i)));
152
+
}
153
+
}
154
+
155
+
EvaluationValue[] parameters = parameterResults.toArray(new EvaluationValue[0]);
156
+
157
+
FunctionIfc function = token.getFunctionDefinition();
158
+
159
+
function.validatePreEvaluation(token, parameters);
160
+
161
+
return function.evaluate(this, token, parameters);
162
+
}
163
+
164
+
private EvaluationValue evaluateArrayIndex(ASTNode startNode) throws EvaluationException {
165
+
EvaluationValue array = evaluateSubtree(startNode.getParameters().get(0));
166
+
EvaluationValue index = evaluateSubtree(startNode.getParameters().get(1));
167
+
168
+
if (array.isArrayValue() && index.isNumberValue()) {
169
+
return array.getArrayValue().get(index.getNumberValue().intValue());
170
+
} else {
171
+
throw EvaluationException.ofUnsupportedDataTypeInOperation(startNode.getToken());
172
+
}
173
+
}
174
+
175
+
private EvaluationValue evaluateStructureSeparator(ASTNode startNode) throws EvaluationException {
176
+
EvaluationValue structure = evaluateSubtree(startNode.getParameters().get(0));
177
+
String name = startNode.getParameters().get(1).getToken().getValue();
178
+
179
+
if (structure.isStructureValue()) {
180
+
return structure.getStructureValue().get(name);
181
+
} else {
182
+
throw EvaluationException.ofUnsupportedDataTypeInOperation(startNode.getToken());
183
+
}
184
+
}
185
+
186
+
/**
187
+
* Rounds the given value, if the decimal places are configured. Also strips trailing decimal
188
+
* zeros, if configured.
189
+
*
190
+
* @param value The input value.
191
+
* @return The rounded value, or the input value if rounding is not configured or possible.
192
+
*/
193
+
private EvaluationValue roundAndStripZerosIfNeeded(EvaluationValue value) {
194
+
BigDecimal bigDecimal = value.getNumberValue();
195
+
if (configuration.getDecimalPlacesRounding()
196
+
!= ExpressionConfiguration.DECIMAL_PLACES_ROUNDING_UNLIMITED) {
197
+
bigDecimal =
198
+
bigDecimal.setScale(
199
+
configuration.getDecimalPlacesRounding(),
200
+
configuration.getMathContext().getRoundingMode());
201
+
}
202
+
if (configuration.isStripTrailingZeros()) {
203
+
bigDecimal = bigDecimal.stripTrailingZeros();
204
+
}
205
+
return new EvaluationValue(bigDecimal);
206
+
}
207
+
208
+
/**
209
+
* Returns the root ode of the parsed abstract syntax tree.
210
+
*
211
+
* @return The abstract syntax tree root node.
212
+
* @throws ParseException If there were problems while parsing the expression.
213
+
*/
214
+
public ASTNode getAbstractSyntaxTree() throws ParseException {
215
+
if (abstractSyntaxTree == null) {
216
+
Tokenizer tokenizer = new Tokenizer(expressionString, configuration);
217
+
ShuntingYardConverter converter =
218
+
new ShuntingYardConverter(expressionString, tokenizer.parse(), configuration);
219
+
abstractSyntaxTree = converter.toAbstractSyntaxTree();
220
+
}
221
+
222
+
return abstractSyntaxTree;
223
+
}
224
+
225
+
/**
226
+
* Validates the expression by parsing it and throwing an exception, if the parser fails.
227
+
*
228
+
* @throws ParseException If there were problems while parsing the expression.
229
+
*/
230
+
public void validate() throws ParseException {
231
+
getAbstractSyntaxTree();
232
+
}
233
+
234
+
/**
235
+
* Adds a variable value to the expression data storage. If a value with the same name already
236
+
* exists, it is overridden. The data type will be determined by examining the passed value
237
+
* object. An exception is thrown, if he found data type is not supported.
238
+
*
239
+
* @param variable The variable name.
240
+
* @param value The variable value.
241
+
* @return The Expression instance, to allow chaining of methods.
242
+
*/
243
+
public Expression with(String variable, Object value) {
244
+
getDataAccessor().setData(variable, new EvaluationValue(value));
245
+
return this;
246
+
}
247
+
248
+
/**
249
+
* Adds a variable value to the expression data storage. If a value with the same name already
250
+
* exists, it is overridden. The data type will be determined by examining the passed value
251
+
* object. An exception is thrown, if he found data type is not supported.
252
+
*
253
+
* @param variable The variable name.
254
+
* @param value The variable value.
255
+
* @return The Expression instance, to allow chaining of methods.
256
+
*/
257
+
public Expression and(String variable, Object value) {
258
+
return with(variable, value);
259
+
}
260
+
261
+
/**
262
+
* Adds all variables values defined in the map with their name (key) and value to the data
263
+
* storage.If a value with the same name already exists, it is overridden. The data type will be
264
+
* determined by examining the passed value object. An exception is thrown, if he found data type
265
+
* is not supported.
266
+
*
267
+
* @param values A map with variable values.
268
+
* @return The Expression instance, to allow chaining of methods.
269
+
*/
270
+
public Expression withValues(Map<String, Object> values) {
271
+
for (Map.Entry<String, Object> entry : values.entrySet()) {
272
+
getDataAccessor().setData(entry.getKey(), new EvaluationValue(entry.getValue()));
273
+
}
274
+
return this;
275
+
}
276
+
277
+
/**
278
+
* Create an AST representation for an expression string. The node can then be used as a
279
+
* sub-expression. Subexpressions are not cached.
280
+
*
281
+
* @param expression The expression string.
282
+
* @return The root node of the expression AST representation.
283
+
* @throws ParseException On any parsing error.
284
+
*/
285
+
public ASTNode createExpressionNode(String expression) throws ParseException {
286
+
Tokenizer tokenizer = new Tokenizer(expression, configuration);
287
+
ShuntingYardConverter converter =
288
+
new ShuntingYardConverter(expression, tokenizer.parse(), configuration);
289
+
return converter.toAbstractSyntaxTree();
290
+
}
291
+
292
+
/**
293
+
* Converts a double value to an {@link EvaluationValue} by considering the configured {@link
294
+
* java.math.MathContext}.
295
+
*
296
+
* @param value The double value to covert.
297
+
* @return An {@link EvaluationValue} of type {@link EvaluationValue.DataType#NUMBER}.
298
+
*/
299
+
public EvaluationValue convertDoubleValue(double value) {
300
+
return new EvaluationValue(value, configuration.getMathContext());
301
+
}
302
+
303
+
/**
304
+
* Returns the list of all nodes of the abstract syntax tree.
305
+
*
306
+
* @return The list of all nodes in the parsed expression.
307
+
* @throws ParseException If there were problems while parsing the expression.
308
+
*/
309
+
public List<ASTNode> getAllASTNodes() throws ParseException {
310
+
return getAllASTNodesForNode(getAbstractSyntaxTree());
311
+
}
312
+
313
+
private List<ASTNode> getAllASTNodesForNode(ASTNode node) {
314
+
List<ASTNode> nodes = new ArrayList<>();
315
+
nodes.add(node);
316
+
for (ASTNode child : node.getParameters()) {
317
+
nodes.addAll(getAllASTNodesForNode(child));
318
+
}
319
+
return nodes;
320
+
}
321
+
322
+
/**
323
+
* Returns all variables or constants that are used i the expression, excluding the standard
324
+
* constants like e.g. <code>PI</code> or <code>TRUE</code> and <code>FALSE</code>.
325
+
*
326
+
* @return All used variables and constants less the standard constants.
327
+
* @throws ParseException If there were problems while parsing the expression.
328
+
*/
329
+
public Set<String> getUsedVariables() throws ParseException {
330
+
Set<String> variables = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
331
+
332
+
for (ASTNode node : getAllASTNodes()) {
333
+
if (node.getToken().getType() == Token.TokenType.VARIABLE_OR_CONSTANT
334
+
&& !configuration.getDefaultConstants().containsKey(node.getToken().getValue())
335
+
&& !variables.contains(node.getToken().getValue())) {
336
+
variables.add(node.getToken().getValue());
337
+
}
338
+
}
339
+
340
+
return variables;
341
+
}
342
+
}
+345
src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java
+345
src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.config;
17
+
18
+
import com.ezylang.evalex.data.DataAccessorIfc;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.data.MapBasedDataAccessor;
21
+
import com.ezylang.evalex.functions.FunctionIfc;
22
+
import com.ezylang.evalex.functions.basic.AbsFunction;
23
+
import com.ezylang.evalex.functions.basic.CeilingFunction;
24
+
import com.ezylang.evalex.functions.basic.FactFunction;
25
+
import com.ezylang.evalex.functions.basic.FloorFunction;
26
+
import com.ezylang.evalex.functions.basic.IfFunction;
27
+
import com.ezylang.evalex.functions.basic.Log10Function;
28
+
import com.ezylang.evalex.functions.basic.LogFunction;
29
+
import com.ezylang.evalex.functions.basic.MaxFunction;
30
+
import com.ezylang.evalex.functions.basic.MinFunction;
31
+
import com.ezylang.evalex.functions.basic.NotFunction;
32
+
import com.ezylang.evalex.functions.basic.RandomFunction;
33
+
import com.ezylang.evalex.functions.basic.RoundFunction;
34
+
import com.ezylang.evalex.functions.basic.SqrtFunction;
35
+
import com.ezylang.evalex.functions.basic.SumFunction;
36
+
import com.ezylang.evalex.functions.string.StringContains;
37
+
import com.ezylang.evalex.functions.string.StringLowerFunction;
38
+
import com.ezylang.evalex.functions.string.StringUpperFunction;
39
+
import com.ezylang.evalex.functions.trigonometric.AcosFunction;
40
+
import com.ezylang.evalex.functions.trigonometric.AcosHFunction;
41
+
import com.ezylang.evalex.functions.trigonometric.AcosRFunction;
42
+
import com.ezylang.evalex.functions.trigonometric.AcotFunction;
43
+
import com.ezylang.evalex.functions.trigonometric.AcotRFunction;
44
+
import com.ezylang.evalex.functions.trigonometric.AsinFunction;
45
+
import com.ezylang.evalex.functions.trigonometric.AsinHFunction;
46
+
import com.ezylang.evalex.functions.trigonometric.AsinRFunction;
47
+
import com.ezylang.evalex.functions.trigonometric.Atan2Function;
48
+
import com.ezylang.evalex.functions.trigonometric.Atan2RFunction;
49
+
import com.ezylang.evalex.functions.trigonometric.AtanFunction;
50
+
import com.ezylang.evalex.functions.trigonometric.AtanHFunction;
51
+
import com.ezylang.evalex.functions.trigonometric.AtanRFunction;
52
+
import com.ezylang.evalex.functions.trigonometric.CosFunction;
53
+
import com.ezylang.evalex.functions.trigonometric.CosHFunction;
54
+
import com.ezylang.evalex.functions.trigonometric.CosRFunction;
55
+
import com.ezylang.evalex.functions.trigonometric.CotFunction;
56
+
import com.ezylang.evalex.functions.trigonometric.CotHFunction;
57
+
import com.ezylang.evalex.functions.trigonometric.CotRFunction;
58
+
import com.ezylang.evalex.functions.trigonometric.CscFunction;
59
+
import com.ezylang.evalex.functions.trigonometric.CscHFunction;
60
+
import com.ezylang.evalex.functions.trigonometric.CscRFunction;
61
+
import com.ezylang.evalex.functions.trigonometric.DegFunction;
62
+
import com.ezylang.evalex.functions.trigonometric.RadFunction;
63
+
import com.ezylang.evalex.functions.trigonometric.SecFunction;
64
+
import com.ezylang.evalex.functions.trigonometric.SecHFunction;
65
+
import com.ezylang.evalex.functions.trigonometric.SecRFunction;
66
+
import com.ezylang.evalex.functions.trigonometric.SinFunction;
67
+
import com.ezylang.evalex.functions.trigonometric.SinHFunction;
68
+
import com.ezylang.evalex.functions.trigonometric.SinRFunction;
69
+
import com.ezylang.evalex.functions.trigonometric.TanFunction;
70
+
import com.ezylang.evalex.functions.trigonometric.TanHFunction;
71
+
import com.ezylang.evalex.functions.trigonometric.TanRFunction;
72
+
import com.ezylang.evalex.operators.OperatorIfc;
73
+
import com.ezylang.evalex.operators.arithmetic.InfixDivisionOperator;
74
+
import com.ezylang.evalex.operators.arithmetic.InfixMinusOperator;
75
+
import com.ezylang.evalex.operators.arithmetic.InfixModuloOperator;
76
+
import com.ezylang.evalex.operators.arithmetic.InfixMultiplicationOperator;
77
+
import com.ezylang.evalex.operators.arithmetic.InfixPlusOperator;
78
+
import com.ezylang.evalex.operators.arithmetic.InfixPowerOfOperator;
79
+
import com.ezylang.evalex.operators.arithmetic.PrefixMinusOperator;
80
+
import com.ezylang.evalex.operators.arithmetic.PrefixPlusOperator;
81
+
import com.ezylang.evalex.operators.booleans.InfixAndOperator;
82
+
import com.ezylang.evalex.operators.booleans.InfixEqualsOperator;
83
+
import com.ezylang.evalex.operators.booleans.InfixGreaterEqualsOperator;
84
+
import com.ezylang.evalex.operators.booleans.InfixGreaterOperator;
85
+
import com.ezylang.evalex.operators.booleans.InfixLessEqualsOperator;
86
+
import com.ezylang.evalex.operators.booleans.InfixLessOperator;
87
+
import com.ezylang.evalex.operators.booleans.InfixNotEqualsOperator;
88
+
import com.ezylang.evalex.operators.booleans.InfixOrOperator;
89
+
import com.ezylang.evalex.operators.booleans.PrefixNotOperator;
90
+
import java.math.BigDecimal;
91
+
import java.math.MathContext;
92
+
import java.math.RoundingMode;
93
+
import java.util.Arrays;
94
+
import java.util.Collections;
95
+
import java.util.Map;
96
+
import java.util.TreeMap;
97
+
import java.util.function.Supplier;
98
+
import lombok.Builder;
99
+
import lombok.Getter;
100
+
101
+
/**
102
+
* The expression configuration can be used to configure various aspects of expression parsing and
103
+
* evaluation. <br>
104
+
* A <code>Builder</code> is provided to create custom configurations, e.g.: <br>
105
+
*
106
+
* <pre>
107
+
* ExpressionConfiguration config = ExpressionConfiguration.builder().mathContext(MathContext.DECIMAL32).arraysAllowed(false).build();
108
+
* </pre>
109
+
*
110
+
* <br>
111
+
* Additional operators and functions can be added to an existing configuration:<br>
112
+
*
113
+
* <pre>
114
+
* ExpressionConfiguration.defaultConfiguration()
115
+
* .withAdditionalOperators(
116
+
* Map.entry("++", new PrefixPlusPlusOperator()),
117
+
* Map.entry("++", new PostfixPlusPlusOperator()))
118
+
* .withAdditionalFunctions(Map.entry("save", new SaveFunction()),
119
+
* Map.entry("update", new UpdateFunction()));
120
+
* </pre>
121
+
*/
122
+
@Builder
123
+
public class ExpressionConfiguration {
124
+
125
+
/** The standard set constants for EvalEx. */
126
+
public static final Map<String, EvaluationValue> StandardConstants =
127
+
Collections.unmodifiableMap(getStandardConstants());
128
+
129
+
/** Setting the decimal places to unlimited, will disable intermediate rounding. */
130
+
public static final int DECIMAL_PLACES_ROUNDING_UNLIMITED = -1;
131
+
132
+
/** The default math context has a precision of 68 and {@link RoundingMode#HALF_EVEN}. */
133
+
public static final MathContext DEFAULT_MATH_CONTEXT =
134
+
new MathContext(68, RoundingMode.HALF_EVEN);
135
+
136
+
/** The operator dictionary holds all operators that will be allowed in an expression. */
137
+
@Builder.Default
138
+
@Getter
139
+
@SuppressWarnings("unchecked")
140
+
private final OperatorDictionaryIfc operatorDictionary =
141
+
MapBasedOperatorDictionary.ofOperators(
142
+
// arithmetic
143
+
Map.entry("+", new PrefixPlusOperator()),
144
+
Map.entry("-", new PrefixMinusOperator()),
145
+
Map.entry("+", new InfixPlusOperator()),
146
+
Map.entry("-", new InfixMinusOperator()),
147
+
Map.entry("*", new InfixMultiplicationOperator()),
148
+
Map.entry("/", new InfixDivisionOperator()),
149
+
Map.entry("^", new InfixPowerOfOperator()),
150
+
Map.entry("%", new InfixModuloOperator()),
151
+
// booleans
152
+
Map.entry("=", new InfixEqualsOperator()),
153
+
Map.entry("==", new InfixEqualsOperator()),
154
+
Map.entry("!=", new InfixNotEqualsOperator()),
155
+
Map.entry("<>", new InfixNotEqualsOperator()),
156
+
Map.entry(">", new InfixGreaterOperator()),
157
+
Map.entry(">=", new InfixGreaterEqualsOperator()),
158
+
Map.entry("<", new InfixLessOperator()),
159
+
Map.entry("<=", new InfixLessEqualsOperator()),
160
+
Map.entry("&&", new InfixAndOperator()),
161
+
Map.entry("||", new InfixOrOperator()),
162
+
Map.entry("!", new PrefixNotOperator()));
163
+
164
+
/** The function dictionary holds all functions that will be allowed in an expression. */
165
+
@Builder.Default
166
+
@Getter
167
+
@SuppressWarnings("unchecked")
168
+
private final FunctionDictionaryIfc functionDictionary =
169
+
MapBasedFunctionDictionary.ofFunctions(
170
+
// basic functions
171
+
Map.entry("ABS", new AbsFunction()),
172
+
Map.entry("CEILING", new CeilingFunction()),
173
+
Map.entry("FACT", new FactFunction()),
174
+
Map.entry("FLOOR", new FloorFunction()),
175
+
Map.entry("IF", new IfFunction()),
176
+
Map.entry("LOG", new LogFunction()),
177
+
Map.entry("LOG10", new Log10Function()),
178
+
Map.entry("MAX", new MaxFunction()),
179
+
Map.entry("MIN", new MinFunction()),
180
+
Map.entry("NOT", new NotFunction()),
181
+
Map.entry("RANDOM", new RandomFunction()),
182
+
Map.entry("ROUND", new RoundFunction()),
183
+
Map.entry("SUM", new SumFunction()),
184
+
Map.entry("SQRT", new SqrtFunction()),
185
+
// trigonometric
186
+
Map.entry("ACOS", new AcosFunction()),
187
+
Map.entry("ACOSH", new AcosHFunction()),
188
+
Map.entry("ACOSR", new AcosRFunction()),
189
+
Map.entry("ACOT", new AcotFunction()),
190
+
Map.entry("ACOTR", new AcotRFunction()),
191
+
Map.entry("ASIN", new AsinFunction()),
192
+
Map.entry("ASINH", new AsinHFunction()),
193
+
Map.entry("ASINR", new AsinRFunction()),
194
+
Map.entry("ATAN", new AtanFunction()),
195
+
Map.entry("ATAN2", new Atan2Function()),
196
+
Map.entry("ATAN2R", new Atan2RFunction()),
197
+
Map.entry("ATANH", new AtanHFunction()),
198
+
Map.entry("ATANR", new AtanRFunction()),
199
+
Map.entry("COS", new CosFunction()),
200
+
Map.entry("COSH", new CosHFunction()),
201
+
Map.entry("COSR", new CosRFunction()),
202
+
Map.entry("COT", new CotFunction()),
203
+
Map.entry("COTH", new CotHFunction()),
204
+
Map.entry("COTR", new CotRFunction()),
205
+
Map.entry("CSC", new CscFunction()),
206
+
Map.entry("CSCH", new CscHFunction()),
207
+
Map.entry("CSCR", new CscRFunction()),
208
+
Map.entry("DEG", new DegFunction()),
209
+
Map.entry("RAD", new RadFunction()),
210
+
Map.entry("SIN", new SinFunction()),
211
+
Map.entry("SINH", new SinHFunction()),
212
+
Map.entry("SINR", new SinRFunction()),
213
+
Map.entry("SEC", new SecFunction()),
214
+
Map.entry("SECH", new SecHFunction()),
215
+
Map.entry("SECR", new SecRFunction()),
216
+
Map.entry("TAN", new TanFunction()),
217
+
Map.entry("TANH", new TanHFunction()),
218
+
Map.entry("TANR", new TanRFunction()),
219
+
// string functions
220
+
Map.entry("STR_CONTAINS", new StringContains()),
221
+
Map.entry("STR_LOWER", new StringLowerFunction()),
222
+
Map.entry("STR_UPPER", new StringUpperFunction()));
223
+
224
+
/** The math context to use. */
225
+
@Builder.Default @Getter private final MathContext mathContext = DEFAULT_MATH_CONTEXT;
226
+
227
+
/**
228
+
* The data accessor is responsible for accessing variable and constant values in an expression.
229
+
* The supplier will be called once for each new expression, the default is to create a new {@link
230
+
* MapBasedDataAccessor} instance for each expression, providing a new storage for each
231
+
* expression.
232
+
*/
233
+
@Builder.Default @Getter
234
+
private final Supplier<DataAccessorIfc> dataAccessorSupplier = MapBasedDataAccessor::new;
235
+
236
+
/**
237
+
* Default constants will be added automatically to each expression and can be used in expression
238
+
* evaluation.
239
+
*/
240
+
@Builder.Default @Getter
241
+
private final Map<String, EvaluationValue> defaultConstants = getStandardConstants();
242
+
243
+
/** Support for arrays in expressions are allowed or not. */
244
+
@Builder.Default @Getter private final boolean arraysAllowed = true;
245
+
246
+
/** Support for structures in expressions are allowed or not. */
247
+
@Builder.Default @Getter private final boolean structuresAllowed = true;
248
+
249
+
/** Support for implicit multiplication, like in (a+b)(b+c) are allowed or not. */
250
+
@Builder.Default @Getter private final boolean implicitMultiplicationAllowed = true;
251
+
252
+
/**
253
+
* The power of operator precedence, can be set higher {@link
254
+
* OperatorIfc#OPERATOR_PRECEDENCE_POWER_HIGHER} or to a custom value.
255
+
*/
256
+
@Builder.Default @Getter
257
+
private final int powerOfPrecedence = OperatorIfc.OPERATOR_PRECEDENCE_POWER;
258
+
259
+
/**
260
+
* If specified, all results from operations and functions will be rounded to the specified number
261
+
* of decimal digits, using the MathContexts rounding mode.
262
+
*/
263
+
@Builder.Default @Getter
264
+
private final int decimalPlacesRounding = DECIMAL_PLACES_ROUNDING_UNLIMITED;
265
+
266
+
/**
267
+
* If set to true (default), then the trailing decimal zeros in a number result will be stripped.
268
+
*/
269
+
@Builder.Default @Getter private final boolean stripTrailingZeros = true;
270
+
271
+
/**
272
+
* Convenience method to create a default configuration.
273
+
*
274
+
* @return A configuration with default settings.
275
+
*/
276
+
public static ExpressionConfiguration defaultConfiguration() {
277
+
return ExpressionConfiguration.builder().build();
278
+
}
279
+
280
+
/**
281
+
* Adds additional operators to this configuration.
282
+
*
283
+
* @param operators variable number of arguments with a map entry holding the operator name and
284
+
* implementation. <br>
285
+
* Example:
286
+
* <pre>
287
+
* ExpressionConfiguration.defaultConfiguration()
288
+
* .withAdditionalOperators(
289
+
* Map.entry("++", new PrefixPlusPlusOperator()),
290
+
* Map.entry("++", new PostfixPlusPlusOperator()));
291
+
* </pre>
292
+
*
293
+
* @return The modified configuration, to allow chaining of methods.
294
+
*/
295
+
@SafeVarargs
296
+
public final ExpressionConfiguration withAdditionalOperators(
297
+
Map.Entry<String, OperatorIfc>... operators) {
298
+
Arrays.stream(operators)
299
+
.forEach(entry -> operatorDictionary.addOperator(entry.getKey(), entry.getValue()));
300
+
return this;
301
+
}
302
+
303
+
/**
304
+
* Adds additional functions to this configuration.
305
+
*
306
+
* @param functions variable number of arguments with a map entry holding the functions name and
307
+
* implementation. <br>
308
+
* Example:
309
+
* <pre>
310
+
* ExpressionConfiguration.defaultConfiguration()
311
+
* .withAdditionalFunctions(
312
+
* Map.entry("save", new SaveFunction()),
313
+
* Map.entry("update", new UpdateFunction()));
314
+
* </pre>
315
+
*
316
+
* @return The modified configuration, to allow chaining of methods.
317
+
*/
318
+
@SafeVarargs
319
+
public final ExpressionConfiguration withAdditionalFunctions(
320
+
Map.Entry<String, FunctionIfc>... functions) {
321
+
Arrays.stream(functions)
322
+
.forEach(entry -> functionDictionary.addFunction(entry.getKey(), entry.getValue()));
323
+
return this;
324
+
}
325
+
326
+
private static Map<String, EvaluationValue> getStandardConstants() {
327
+
328
+
Map<String, EvaluationValue> constants = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
329
+
330
+
constants.put("TRUE", new EvaluationValue(true));
331
+
constants.put("FALSE", new EvaluationValue(false));
332
+
constants.put(
333
+
"PI",
334
+
new EvaluationValue(
335
+
new BigDecimal(
336
+
"3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679")));
337
+
constants.put(
338
+
"E",
339
+
new EvaluationValue(
340
+
new BigDecimal(
341
+
"2.71828182845904523536028747135266249775724709369995957496696762772407663")));
342
+
343
+
return constants;
344
+
}
345
+
}
+52
src/main/java/com/ezylang/evalex/config/FunctionDictionaryIfc.java
+52
src/main/java/com/ezylang/evalex/config/FunctionDictionaryIfc.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.config;
17
+
18
+
import com.ezylang.evalex.functions.FunctionIfc;
19
+
20
+
/**
21
+
* A function dictionary holds all the functions, that can be used in an expression. <br>
22
+
* The default implementation is the {@link MapBasedFunctionDictionary}.
23
+
*/
24
+
public interface FunctionDictionaryIfc {
25
+
26
+
/**
27
+
* Allows to add a function to the dictionary. Implementation is optional, if you have a fixed set
28
+
* of functions, this method can throw an exception.
29
+
*
30
+
* @param functionName The function name.
31
+
* @param function The function implementation.
32
+
*/
33
+
void addFunction(String functionName, FunctionIfc function);
34
+
35
+
/**
36
+
* Check if the dictionary has a function with that name.
37
+
*
38
+
* @param functionName The function name to look for.
39
+
* @return <code>true</code> if a function was found or <code>false</code> if not.
40
+
*/
41
+
default boolean hasFunction(String functionName) {
42
+
return getFunction(functionName) != null;
43
+
}
44
+
45
+
/**
46
+
* Get the function definition for a function name.
47
+
*
48
+
* @param functionName The name of the function.
49
+
* @return The function definition or <code>null</code> if no function was found.
50
+
*/
51
+
FunctionIfc getFunction(String functionName);
52
+
}
+55
src/main/java/com/ezylang/evalex/config/MapBasedFunctionDictionary.java
+55
src/main/java/com/ezylang/evalex/config/MapBasedFunctionDictionary.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.config;
17
+
18
+
import static java.util.Arrays.stream;
19
+
20
+
import com.ezylang.evalex.functions.FunctionIfc;
21
+
import java.util.Map;
22
+
import java.util.TreeMap;
23
+
24
+
/**
25
+
* A default case-insensitive implementation of the function dictionary that uses a local <code>
26
+
* Map.Entry<String, FunctionIfc></code> for storage.
27
+
*/
28
+
public class MapBasedFunctionDictionary implements FunctionDictionaryIfc {
29
+
30
+
private final Map<String, FunctionIfc> functions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
31
+
32
+
/**
33
+
* Creates a new function dictionary with the specified list of functions.
34
+
*
35
+
* @param functions variable number of arguments that specify the function names and definitions
36
+
* that will initially be added.
37
+
* @return A newly created function dictionary with the specified functions.
38
+
*/
39
+
@SuppressWarnings({"unchecked", "varargs"})
40
+
public static FunctionDictionaryIfc ofFunctions(Map.Entry<String, FunctionIfc>... functions) {
41
+
FunctionDictionaryIfc dictionary = new MapBasedFunctionDictionary();
42
+
stream(functions).forEach(entry -> dictionary.addFunction(entry.getKey(), entry.getValue()));
43
+
return dictionary;
44
+
}
45
+
46
+
@Override
47
+
public FunctionIfc getFunction(String functionName) {
48
+
return functions.get(functionName);
49
+
}
50
+
51
+
@Override
52
+
public void addFunction(String functionName, FunctionIfc function) {
53
+
functions.put(functionName, function);
54
+
}
55
+
}
+75
src/main/java/com/ezylang/evalex/config/MapBasedOperatorDictionary.java
+75
src/main/java/com/ezylang/evalex/config/MapBasedOperatorDictionary.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.config;
17
+
18
+
import static java.util.Arrays.stream;
19
+
20
+
import com.ezylang.evalex.operators.OperatorIfc;
21
+
import java.util.Map;
22
+
import java.util.TreeMap;
23
+
24
+
/**
25
+
* A default case-insensitive implementation of the operator dictionary that uses a local <code>
26
+
* Map.Entry<String,OperatorIfc></code> for storage.
27
+
*/
28
+
public class MapBasedOperatorDictionary implements OperatorDictionaryIfc {
29
+
30
+
final Map<String, OperatorIfc> prefixOperators = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
31
+
32
+
final Map<String, OperatorIfc> postfixOperators = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
33
+
34
+
final Map<String, OperatorIfc> infixOperators = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
35
+
36
+
/**
37
+
* Creates a new operator dictionary with the specified list of operators.
38
+
*
39
+
* @param operators variable number of arguments that specify the operator names and definitions
40
+
* that will initially be added.
41
+
* @return A newly created operator dictionary with the specified operators.
42
+
*/
43
+
@SuppressWarnings({"unchecked", "varargs"})
44
+
public static OperatorDictionaryIfc ofOperators(Map.Entry<String, OperatorIfc>... operators) {
45
+
OperatorDictionaryIfc dictionary = new MapBasedOperatorDictionary();
46
+
stream(operators).forEach(entry -> dictionary.addOperator(entry.getKey(), entry.getValue()));
47
+
return dictionary;
48
+
}
49
+
50
+
@Override
51
+
public void addOperator(String operatorString, OperatorIfc operator) {
52
+
if (operator.isPrefix()) {
53
+
prefixOperators.put(operatorString, operator);
54
+
} else if (operator.isPostfix()) {
55
+
postfixOperators.put(operatorString, operator);
56
+
} else {
57
+
infixOperators.put(operatorString, operator);
58
+
}
59
+
}
60
+
61
+
@Override
62
+
public OperatorIfc getPrefixOperator(String operatorString) {
63
+
return prefixOperators.get(operatorString);
64
+
}
65
+
66
+
@Override
67
+
public OperatorIfc getPostfixOperator(String operatorString) {
68
+
return postfixOperators.get(operatorString);
69
+
}
70
+
71
+
@Override
72
+
public OperatorIfc getInfixOperator(String operatorString) {
73
+
return infixOperators.get(operatorString);
74
+
}
75
+
}
+88
src/main/java/com/ezylang/evalex/config/OperatorDictionaryIfc.java
+88
src/main/java/com/ezylang/evalex/config/OperatorDictionaryIfc.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.config;
17
+
18
+
import com.ezylang.evalex.operators.OperatorIfc;
19
+
20
+
/**
21
+
* An operator dictionary holds all the operators, that can be used in an expression. <br>
22
+
* The default implementation is the {@link MapBasedOperatorDictionary}.
23
+
*/
24
+
public interface OperatorDictionaryIfc {
25
+
26
+
/**
27
+
* Allows to add an operator to the dictionary. Implementation is optional, if you have a fixed
28
+
* set of operators, this method can throw an exception.
29
+
*
30
+
* @param operatorString The operator name.
31
+
* @param operator The operator implementation.
32
+
*/
33
+
void addOperator(String operatorString, OperatorIfc operator);
34
+
35
+
/**
36
+
* Check if the dictionary has a prefix operator with that name.
37
+
*
38
+
* @param operatorString The operator name to look for.
39
+
* @return <code>true</code> if an operator was found or <code>false</code> if not.
40
+
*/
41
+
default boolean hasPrefixOperator(String operatorString) {
42
+
return getPrefixOperator(operatorString) != null;
43
+
}
44
+
45
+
/**
46
+
* Check if the dictionary has a postfix operator with that name.
47
+
*
48
+
* @param operatorString The operator name to look for.
49
+
* @return <code>true</code> if an operator was found or <code>false</code> if not.
50
+
*/
51
+
default boolean hasPostfixOperator(String operatorString) {
52
+
return getPostfixOperator(operatorString) != null;
53
+
}
54
+
55
+
/**
56
+
* Check if the dictionary has an infix operator with that name.
57
+
*
58
+
* @param operatorString The operator name to look for.
59
+
* @return <code>true</code> if an operator was found or <code>false</code> if not.
60
+
*/
61
+
default boolean hasInfixOperator(String operatorString) {
62
+
return getInfixOperator(operatorString) != null;
63
+
}
64
+
65
+
/**
66
+
* Get the operator definition for a prefix operator name.
67
+
*
68
+
* @param operatorString The name of the operator.
69
+
* @return The operator definition or <code>null</code> if no operator was found.
70
+
*/
71
+
OperatorIfc getPrefixOperator(String operatorString);
72
+
73
+
/**
74
+
* Get the operator definition for a postfix operator name.
75
+
*
76
+
* @param operatorString The name of the operator.
77
+
* @return The operator definition or <code>null</code> if no operator was found.
78
+
*/
79
+
OperatorIfc getPostfixOperator(String operatorString);
80
+
81
+
/**
82
+
* Get the operator definition for an infix operator name.
83
+
*
84
+
* @param operatorString The name of the operator.
85
+
* @return The operator definition or <code>null</code> if no operator was found.
86
+
*/
87
+
OperatorIfc getInfixOperator(String operatorString);
88
+
}
+40
src/main/java/com/ezylang/evalex/data/DataAccessorIfc.java
+40
src/main/java/com/ezylang/evalex/data/DataAccessorIfc.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.data;
17
+
18
+
/**
19
+
* A data accessor is responsible for accessing data, e.g. variable and constant values during an
20
+
* expression evaluation. The default implementation for setting and reading local data is the
21
+
* {@link MapBasedDataAccessor}.
22
+
*/
23
+
public interface DataAccessorIfc {
24
+
25
+
/**
26
+
* Retrieves a data value.
27
+
*
28
+
* @param variable The variable name, e.g. a variable or constant name.
29
+
* @return The data value, or <code>null</code> if not found.
30
+
*/
31
+
EvaluationValue getData(String variable);
32
+
33
+
/**
34
+
* Sets a data value.
35
+
*
36
+
* @param variable The variable name, e.g. a variable or constant name.
37
+
* @param value The value to set.
38
+
*/
39
+
void setData(String variable, EvaluationValue value);
40
+
}
+363
src/main/java/com/ezylang/evalex/data/EvaluationValue.java
+363
src/main/java/com/ezylang/evalex/data/EvaluationValue.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.data;
17
+
18
+
import com.ezylang.evalex.parser.ASTNode;
19
+
import java.math.BigDecimal;
20
+
import java.math.BigInteger;
21
+
import java.math.MathContext;
22
+
import java.util.ArrayList;
23
+
import java.util.Collections;
24
+
import java.util.HashMap;
25
+
import java.util.List;
26
+
import java.util.Map;
27
+
import java.util.Map.Entry;
28
+
import lombok.Value;
29
+
30
+
/**
31
+
* The representation of the final or intermediate evaluation result value. The representation
32
+
* consists of a data type and data value. Depending on the type, the value will be stored in a
33
+
* corresponding object type.
34
+
*/
35
+
@Value
36
+
public class EvaluationValue implements Comparable<EvaluationValue> {
37
+
38
+
/** The supported data types. */
39
+
public enum DataType {
40
+
/** A string of characters, stored as {@link String}. */
41
+
STRING,
42
+
/** Any number, stored as {@link BigDecimal}. */
43
+
NUMBER,
44
+
/** A boolean, stored as {@link Boolean}. */
45
+
BOOLEAN,
46
+
/** A list evaluation values. Stored as {@link java.util.List<EvaluationValue>}. */
47
+
ARRAY,
48
+
/**
49
+
* A structure with pairs of name/value members. Name is a string and the value is a {@link
50
+
* EvaluationValue}. Stored as a {@link java.util.Map}.
51
+
*/
52
+
STRUCTURE,
53
+
/**
54
+
* Used for lazy parameter evaluation, stored as an {@link ASTNode}, which can be evaluated on
55
+
* demand.
56
+
*/
57
+
EXPRESSION_NODE
58
+
}
59
+
60
+
Object value;
61
+
62
+
DataType dataType;
63
+
64
+
/**
65
+
* Creates a new evaluation value by taking a good guess on the provided Java class and converting
66
+
* it to one of the supported types.
67
+
*
68
+
* <table>
69
+
* <tr>
70
+
* <th>Input type</th><th>Storage Type</th>
71
+
* </tr>
72
+
* <tr><td>BigDecimal</td><td>BigDecimal</td></tr>
73
+
* <tr><td>Long, long</td><td>BigDecimal</td></tr>
74
+
* <tr><td>Integer, int</td><td>BigDecimal</td></tr>
75
+
* <tr><td>Short, short</td><td>BigDecimal</td></tr>
76
+
* <tr><td>Byte, byte</td><td>BigDecimal</td></tr>
77
+
* <tr><td>Double, double</td><td>BigDecimal *</td></tr>
78
+
* <tr><td>Float, float</td><td>BigDecimal *</td></tr>
79
+
* <tr><td>CharSequence , String</td><td>String</td></tr>
80
+
* <tr><td>Boolean, boolean</td><td>Boolean</td></tr>
81
+
* <tr><td>ASTNode</td><td>ASTNode</td></tr>
82
+
* <tr><td>List<?></td><td>List<EvaluationValue> - each entry will be converted</td></tr>
83
+
* <tr><td>Map<?,?></td><td>Map<String><EvaluationValue> - each entry will be converted.</td></tr>
84
+
* </table>
85
+
*
86
+
* <i>* Be careful with conversion problems when using float or double, which are fractional
87
+
* numbers. A (float)0.1 is e.g. converted to 0.10000000149011612</i>
88
+
*
89
+
* @param value One of the supported data types.
90
+
* @throws IllegalArgumentException if the data type can't be mapped.
91
+
*/
92
+
public EvaluationValue(Object value) {
93
+
BigDecimal number = convertToBigDecimal(value);
94
+
if (number != null) {
95
+
this.dataType = DataType.NUMBER;
96
+
this.value = number;
97
+
} else if (value instanceof CharSequence) {
98
+
this.dataType = DataType.STRING;
99
+
this.value = ((CharSequence) value).toString();
100
+
} else if (value instanceof Character) {
101
+
this.dataType = DataType.STRING;
102
+
this.value = ((Character) value).toString();
103
+
} else if (value instanceof Boolean) {
104
+
this.dataType = DataType.BOOLEAN;
105
+
this.value = value;
106
+
} else if (value instanceof ASTNode) {
107
+
this.dataType = DataType.EXPRESSION_NODE;
108
+
this.value = value;
109
+
} else if (value instanceof List) {
110
+
this.dataType = DataType.ARRAY;
111
+
this.value = convertToList((List<?>) value);
112
+
} else if (value instanceof Map) {
113
+
this.dataType = DataType.STRUCTURE;
114
+
this.value = convertMapStructure((Map<?, ?>) value);
115
+
} else {
116
+
throw new IllegalArgumentException(
117
+
"Unsupported data type '" + value.getClass().getName() + "'");
118
+
}
119
+
}
120
+
121
+
public EvaluationValue(double value, MathContext mathContext) {
122
+
this.dataType = DataType.NUMBER;
123
+
this.value = new BigDecimal(Double.toString(value), mathContext);
124
+
}
125
+
126
+
/**
127
+
* Converts a {@link Map} of objects to a {@link Map} of {@link EvaluationValue} values.
128
+
*
129
+
* @return A {@link Map} of {@link EvaluationValue} values.
130
+
*/
131
+
private Map<String, EvaluationValue> convertMapStructure(Map<?, ?> value) {
132
+
Map<String, EvaluationValue> structure = new HashMap<>();
133
+
for (Entry<?, ?> entry : value.entrySet()) {
134
+
String name = entry.getKey().toString();
135
+
structure.put(name, new EvaluationValue(entry.getValue()));
136
+
}
137
+
return structure;
138
+
}
139
+
140
+
/**
141
+
* Converts a {@link List} of objects to a {@link List} of {@link EvaluationValue} values.
142
+
*
143
+
* @return A {@link List} of {@link EvaluationValue} values.
144
+
*/
145
+
private List<EvaluationValue> convertToList(List<?> value) {
146
+
List<EvaluationValue> array = new ArrayList<>();
147
+
value.forEach(element -> array.add(new EvaluationValue(element)));
148
+
return array;
149
+
}
150
+
151
+
/**
152
+
* Check and convert, if an {@link Object} can be converted to a {@link BigDecimal} value.
153
+
*
154
+
* @return A {@link BigDecimal} value of the object, or <code>null</code> if it can't be
155
+
* converted.
156
+
*/
157
+
private BigDecimal convertToBigDecimal(Object value) {
158
+
if (value instanceof BigDecimal) {
159
+
return (BigDecimal) value;
160
+
} else if (value instanceof Double) {
161
+
return BigDecimal.valueOf((double) value);
162
+
} else if (value instanceof Float) {
163
+
return BigDecimal.valueOf((float) value);
164
+
} else if (value instanceof Integer) {
165
+
return BigDecimal.valueOf((int) value);
166
+
} else if (value instanceof Long) {
167
+
return BigDecimal.valueOf((long) value);
168
+
} else if (value instanceof Short) {
169
+
return BigDecimal.valueOf((short) value);
170
+
} else if (value instanceof Byte) {
171
+
return BigDecimal.valueOf((byte) value);
172
+
} else {
173
+
return null;
174
+
}
175
+
}
176
+
177
+
/**
178
+
* Checks if the value is of type {@link DataType#NUMBER}.
179
+
*
180
+
* @return <code>true</code> or <code>false</code>.
181
+
*/
182
+
public boolean isNumberValue() {
183
+
return getDataType() == DataType.NUMBER;
184
+
}
185
+
186
+
/**
187
+
* Checks if the value is of type {@link DataType#STRING}.
188
+
*
189
+
* @return <code>true</code> or <code>false</code>.
190
+
*/
191
+
public boolean isStringValue() {
192
+
return getDataType() == DataType.STRING;
193
+
}
194
+
195
+
/**
196
+
* Checks if the value is of type {@link DataType#BOOLEAN}.
197
+
*
198
+
* @return <code>true</code> or <code>false</code>.
199
+
*/
200
+
public boolean isBooleanValue() {
201
+
return getDataType() == DataType.BOOLEAN;
202
+
}
203
+
204
+
/**
205
+
* Checks if the value is of type {@link DataType#ARRAY}.
206
+
*
207
+
* @return <code>true</code> or <code>false</code>.
208
+
*/
209
+
public boolean isArrayValue() {
210
+
return getDataType() == DataType.ARRAY;
211
+
}
212
+
213
+
/**
214
+
* Checks if the value is of type {@link DataType#STRUCTURE}.
215
+
*
216
+
* @return <code>true</code> or <code>false</code>.
217
+
*/
218
+
public boolean isStructureValue() {
219
+
return getDataType() == DataType.STRUCTURE;
220
+
}
221
+
222
+
/**
223
+
* Checks if the value is of type {@link DataType#EXPRESSION_NODE}.
224
+
*
225
+
* @return <code>true</code> or <code>false</code>.
226
+
*/
227
+
public boolean isExpressionNode() {
228
+
return getDataType() == DataType.EXPRESSION_NODE;
229
+
}
230
+
231
+
/**
232
+
* Creates a {@link DataType#NUMBER} value from a {@link String}.
233
+
*
234
+
* @param value The {@link String} value.
235
+
* @param mathContext The math context to use for creation of the {@link BigDecimal} storage.
236
+
*/
237
+
public static EvaluationValue numberOfString(String value, MathContext mathContext) {
238
+
if (value.startsWith("0x") || value.startsWith("0X")) {
239
+
BigInteger hexToInteger = new BigInteger(value.substring(2), 16);
240
+
return new EvaluationValue(new BigDecimal(hexToInteger, mathContext));
241
+
} else {
242
+
return new EvaluationValue(new BigDecimal(value, mathContext));
243
+
}
244
+
}
245
+
246
+
/**
247
+
* Gets a {@link BigDecimal} representation of the value. If possible and needed, a conversion
248
+
* will be made.
249
+
*
250
+
* <ul>
251
+
* <li>Boolean <code>true</code> will return a {@link BigDecimal#ONE}, else {@link
252
+
* BigDecimal#ZERO}.
253
+
* </ul>
254
+
*
255
+
* @return The {@link BigDecimal} representation of the value, or {@link BigDecimal#ZERO} if
256
+
* conversion is not possible.
257
+
*/
258
+
public BigDecimal getNumberValue() {
259
+
switch (getDataType()) {
260
+
case NUMBER:
261
+
return (BigDecimal) value;
262
+
case BOOLEAN:
263
+
return (Boolean.TRUE.equals(value) ? BigDecimal.ONE : BigDecimal.ZERO);
264
+
case STRING:
265
+
return Boolean.parseBoolean((String) value) ? BigDecimal.ONE : BigDecimal.ZERO;
266
+
default:
267
+
return BigDecimal.ZERO;
268
+
}
269
+
}
270
+
271
+
/**
272
+
* Gets a {@link String} representation of the value. If possible and needed, a conversion will be
273
+
* made.
274
+
*
275
+
* <ul>
276
+
* <li>Number values will be returned as {@link BigDecimal#toPlainString()}.
277
+
* <li>The {@link Object#toString()} will be used in all other cases.
278
+
* </ul>
279
+
*
280
+
* @return The {@link String} representation of the value.
281
+
*/
282
+
public String getStringValue() {
283
+
if (getDataType() == DataType.NUMBER) {
284
+
return ((BigDecimal) value).toPlainString();
285
+
}
286
+
return value.toString();
287
+
}
288
+
289
+
/**
290
+
* Gets a {@link Boolean} representation of the value. If possible and needed, a conversion will
291
+
* be made.
292
+
*
293
+
* <ul>
294
+
* <li>Any non-zero number value will return true.
295
+
* <li>Any string with the value <code>"true"</code> (case ignored) will return true.
296
+
* </ul>
297
+
*
298
+
* @return The {@link Boolean} representation of the value.
299
+
*/
300
+
public Boolean getBooleanValue() {
301
+
switch (getDataType()) {
302
+
case NUMBER:
303
+
return !value.equals(BigDecimal.ZERO);
304
+
case BOOLEAN:
305
+
return (Boolean) value;
306
+
case STRING:
307
+
return Boolean.parseBoolean((String) value);
308
+
default:
309
+
return false;
310
+
}
311
+
}
312
+
313
+
/**
314
+
* Gets a {@link List<EvaluationValue>} representation of the value.
315
+
*
316
+
* @return The {@link List<EvaluationValue>} representation of the value or an empty list, if no
317
+
* conversion is possible.
318
+
*/
319
+
@SuppressWarnings("unchecked")
320
+
public List<EvaluationValue> getArrayValue() {
321
+
if (isArrayValue()) {
322
+
return (List<EvaluationValue>) value;
323
+
} else {
324
+
return Collections.emptyList();
325
+
}
326
+
}
327
+
328
+
/**
329
+
* Gets a {@link Map} representation of the value.
330
+
*
331
+
* @return The {@link Map} representation of the value or an empty list, if no conversion is
332
+
* possible.
333
+
*/
334
+
@SuppressWarnings("unchecked")
335
+
public Map<String, EvaluationValue> getStructureValue() {
336
+
if (isStructureValue()) {
337
+
return (Map<String, EvaluationValue>) value;
338
+
} else {
339
+
return Collections.emptyMap();
340
+
}
341
+
}
342
+
343
+
/**
344
+
* Gets the expression node, if this value is of type {@link DataType#EXPRESSION_NODE}.
345
+
*
346
+
* @return The expression node, or null for any other data type.
347
+
*/
348
+
public ASTNode getExpressionNode() {
349
+
return isExpressionNode() ? ((ASTNode) getValue()) : null;
350
+
}
351
+
352
+
@Override
353
+
public int compareTo(EvaluationValue toCompare) {
354
+
switch (getDataType()) {
355
+
case NUMBER:
356
+
return getNumberValue().compareTo(toCompare.getNumberValue());
357
+
case BOOLEAN:
358
+
return getBooleanValue().compareTo(toCompare.getBooleanValue());
359
+
default:
360
+
return getStringValue().compareTo(toCompare.getStringValue());
361
+
}
362
+
}
363
+
}
+39
src/main/java/com/ezylang/evalex/data/MapBasedDataAccessor.java
+39
src/main/java/com/ezylang/evalex/data/MapBasedDataAccessor.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.data;
17
+
18
+
import java.util.Map;
19
+
import java.util.TreeMap;
20
+
21
+
/**
22
+
* A default case-insensitive implementation of the data accessor that uses a local <code>
23
+
* Map.Entry<String, EvaluationValue></code> for storage.
24
+
*/
25
+
public class MapBasedDataAccessor implements DataAccessorIfc {
26
+
27
+
private final Map<String, EvaluationValue> variables =
28
+
new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
29
+
30
+
@Override
31
+
public EvaluationValue getData(String variable) {
32
+
return variables.get(variable);
33
+
}
34
+
35
+
@Override
36
+
public void setData(String variable, EvaluationValue value) {
37
+
variables.put(variable, value);
38
+
}
39
+
}
+99
src/main/java/com/ezylang/evalex/functions/AbstractFunction.java
+99
src/main/java/com/ezylang/evalex/functions/AbstractFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions;
17
+
18
+
import com.ezylang.evalex.EvaluationException;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.parser.Token;
21
+
import java.math.BigDecimal;
22
+
import java.util.ArrayList;
23
+
import java.util.List;
24
+
25
+
/**
26
+
* Abstract implementation of the {@link FunctionIfc}, used as base class for function
27
+
* implementations.
28
+
*/
29
+
public abstract class AbstractFunction implements FunctionIfc {
30
+
31
+
private final List<FunctionParameterDefinition> functionParameterDefinitions = new ArrayList<>();
32
+
33
+
private final boolean hasVarArgs;
34
+
35
+
/**
36
+
* Creates a new function and uses the {@link FunctionParameter} annotations to create the
37
+
* parameter definitions.
38
+
*/
39
+
protected AbstractFunction() {
40
+
FunctionParameter[] parameterAnnotations =
41
+
getClass().getAnnotationsByType(FunctionParameter.class);
42
+
43
+
boolean varArgParameterFound = false;
44
+
45
+
for (FunctionParameter parameter : parameterAnnotations) {
46
+
if (varArgParameterFound) {
47
+
throw new IllegalArgumentException(
48
+
"Only last parameter may be defined as variable argument");
49
+
}
50
+
if (parameter.isVarArg()) {
51
+
varArgParameterFound = true;
52
+
}
53
+
functionParameterDefinitions.add(
54
+
FunctionParameterDefinition.builder()
55
+
.name(parameter.name())
56
+
.isVarArg(parameter.isVarArg())
57
+
.isLazy(parameter.isLazy())
58
+
.nonZero(parameter.nonZero())
59
+
.nonNegative(parameter.nonNegative())
60
+
.build());
61
+
}
62
+
63
+
hasVarArgs = varArgParameterFound;
64
+
}
65
+
66
+
@Override
67
+
public void validatePreEvaluation(Token token, EvaluationValue... parameterValues)
68
+
throws EvaluationException {
69
+
70
+
for (int i = 0; i < parameterValues.length; i++) {
71
+
FunctionParameterDefinition definition = getParameterDefinitionForParameter(i);
72
+
if (definition.isNonZero() && parameterValues[i].getNumberValue().equals(BigDecimal.ZERO)) {
73
+
throw new EvaluationException(token, "Parameter must not be zero");
74
+
}
75
+
if (definition.isNonNegative() && parameterValues[i].getNumberValue().signum() < 0) {
76
+
throw new EvaluationException(token, "Parameter must not be negative");
77
+
}
78
+
}
79
+
}
80
+
81
+
@Override
82
+
public List<FunctionParameterDefinition> getFunctionParameterDefinitions() {
83
+
return functionParameterDefinitions;
84
+
}
85
+
86
+
@Override
87
+
public boolean hasVarArgs() {
88
+
return hasVarArgs;
89
+
}
90
+
91
+
private FunctionParameterDefinition getParameterDefinitionForParameter(int index) {
92
+
93
+
if (hasVarArgs && index >= functionParameterDefinitions.size()) {
94
+
index = functionParameterDefinitions.size() - 1;
95
+
}
96
+
97
+
return functionParameterDefinitions.get(index);
98
+
}
99
+
}
+82
src/main/java/com/ezylang/evalex/functions/FunctionIfc.java
+82
src/main/java/com/ezylang/evalex/functions/FunctionIfc.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions;
17
+
18
+
import com.ezylang.evalex.EvaluationException;
19
+
import com.ezylang.evalex.Expression;
20
+
import com.ezylang.evalex.data.EvaluationValue;
21
+
import com.ezylang.evalex.parser.Token;
22
+
import java.util.List;
23
+
24
+
/**
25
+
* Interface that is required for all functions in a function dictionary for evaluation of
26
+
* expressions.
27
+
*/
28
+
public interface FunctionIfc {
29
+
30
+
/**
31
+
* Returns the list of parameter definitions. Is never empty or <code>null</code>.
32
+
*
33
+
* @return The parameter definition list.
34
+
*/
35
+
List<FunctionParameterDefinition> getFunctionParameterDefinitions();
36
+
37
+
/**
38
+
* Performs the function logic and returns an evaluation result.
39
+
*
40
+
* @param expression The expression, where this function is executed. Can be used to access the
41
+
* expression configuration.
42
+
* @param functionToken The function token from the parsed expression.
43
+
* @param parameterValues The parameter values.
44
+
* @return The evaluation result in form of a {@link EvaluationValue}.
45
+
* @throws EvaluationException In case there were problems during evaluation.
46
+
*/
47
+
EvaluationValue evaluate(
48
+
Expression expression, Token functionToken, EvaluationValue... parameterValues)
49
+
throws EvaluationException;
50
+
51
+
/**
52
+
* Validates the evaluation parameters, called before the actual evaluation.
53
+
*
54
+
* @param token The function token.
55
+
* @param parameterValues The parameter values
56
+
* @throws EvaluationException in case of any validation error
57
+
*/
58
+
void validatePreEvaluation(Token token, EvaluationValue... parameterValues)
59
+
throws EvaluationException;
60
+
61
+
/**
62
+
* Checks whether the function has a variable number of arguments parameter.
63
+
*
64
+
* @return <code>true</code> or <code>false</code>:
65
+
*/
66
+
boolean hasVarArgs();
67
+
68
+
/**
69
+
* Checks if the parameter is a lazy parameter.
70
+
*
71
+
* @param parameterIndex The parameter index, starts at 0 for the first parameter. If the index is
72
+
* bigger than the list of parameter definitions, the last parameter definition will be
73
+
* checked.
74
+
* @return <code>true</code> if the specified parameter is defined as lazy.
75
+
*/
76
+
default boolean isParameterLazy(int parameterIndex) {
77
+
if (parameterIndex >= getFunctionParameterDefinitions().size()) {
78
+
parameterIndex = getFunctionParameterDefinitions().size() - 1;
79
+
}
80
+
return getFunctionParameterDefinitions().get(parameterIndex).isLazy();
81
+
}
82
+
}
+46
src/main/java/com/ezylang/evalex/functions/FunctionParameter.java
+46
src/main/java/com/ezylang/evalex/functions/FunctionParameter.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions;
17
+
18
+
import java.lang.annotation.Documented;
19
+
import java.lang.annotation.ElementType;
20
+
import java.lang.annotation.Repeatable;
21
+
import java.lang.annotation.Retention;
22
+
import java.lang.annotation.RetentionPolicy;
23
+
import java.lang.annotation.Target;
24
+
25
+
/** Annotation to define a function parameter. */
26
+
@Documented
27
+
@Target(ElementType.TYPE)
28
+
@Repeatable(FunctionParameters.class)
29
+
@Retention(RetentionPolicy.RUNTIME)
30
+
public @interface FunctionParameter {
31
+
32
+
/** The parameter name. */
33
+
String name();
34
+
35
+
/** If the parameter is lazily evaluated. Defaults to false. */
36
+
boolean isLazy() default false;
37
+
38
+
/** If the parameter is a variable arg type (repeatable). Defaults to false. */
39
+
boolean isVarArg() default false;
40
+
41
+
/** If the parameter does not allow zero values. */
42
+
boolean nonZero() default false;
43
+
44
+
/** If the parameter does not allow negative values. */
45
+
boolean nonNegative() default false;
46
+
}
+49
src/main/java/com/ezylang/evalex/functions/FunctionParameterDefinition.java
+49
src/main/java/com/ezylang/evalex/functions/FunctionParameterDefinition.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions;
17
+
18
+
import lombok.Builder;
19
+
import lombok.Value;
20
+
21
+
/** Definition of a function parameter. */
22
+
@Value
23
+
@Builder
24
+
public class FunctionParameterDefinition {
25
+
26
+
/** Name of the parameter, useful for error messages etc. */
27
+
String name;
28
+
29
+
/**
30
+
* Whether this parameter is a variable argument parameter (can be repeated).
31
+
*
32
+
* @see com.ezylang.evalex.functions.basic.MinFunction for an example.
33
+
*/
34
+
boolean isVarArg;
35
+
36
+
/**
37
+
* Set to true, the parameter will not be evaluated in advance, but the corresponding {@link
38
+
* com.ezylang.evalex.parser.ASTNode} will be passed as a parameter value.
39
+
*
40
+
* @see com.ezylang.evalex.functions.basic.IfFunction for an example.
41
+
*/
42
+
boolean isLazy;
43
+
44
+
/** If the parameter does not allow zero values. */
45
+
boolean nonZero;
46
+
47
+
/** If the parameter does not allow negative values. */
48
+
boolean nonNegative;
49
+
}
+30
src/main/java/com/ezylang/evalex/functions/FunctionParameters.java
+30
src/main/java/com/ezylang/evalex/functions/FunctionParameters.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions;
17
+
18
+
import java.lang.annotation.Documented;
19
+
import java.lang.annotation.ElementType;
20
+
import java.lang.annotation.Retention;
21
+
import java.lang.annotation.RetentionPolicy;
22
+
import java.lang.annotation.Target;
23
+
24
+
/** Collator for repeatable {@link FunctionParameter} annotations. */
25
+
@Documented
26
+
@Target(ElementType.TYPE)
27
+
@Retention(RetentionPolicy.RUNTIME)
28
+
public @interface FunctionParameters {
29
+
FunctionParameter[] value();
30
+
}
+35
src/main/java/com/ezylang/evalex/functions/basic/AbsFunction.java
+35
src/main/java/com/ezylang/evalex/functions/basic/AbsFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.basic;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Absolute (non-negative) value. */
25
+
@FunctionParameter(name = "value")
26
+
public class AbsFunction extends AbstractFunction {
27
+
28
+
@Override
29
+
public EvaluationValue evaluate(
30
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
31
+
32
+
return new EvaluationValue(
33
+
parameterValues[0].getNumberValue().abs(expression.getConfiguration().getMathContext()));
34
+
}
35
+
}
+36
src/main/java/com/ezylang/evalex/functions/basic/CeilingFunction.java
+36
src/main/java/com/ezylang/evalex/functions/basic/CeilingFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.basic;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
import java.math.RoundingMode;
24
+
25
+
/** Rounds the given value an integer using the rounding mode {@link RoundingMode#CEILING} */
26
+
@FunctionParameter(name = "value")
27
+
public class CeilingFunction extends AbstractFunction {
28
+
@Override
29
+
public EvaluationValue evaluate(
30
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
31
+
32
+
EvaluationValue value = parameterValues[0];
33
+
34
+
return new EvaluationValue(value.getNumberValue().setScale(0, RoundingMode.CEILING));
35
+
}
36
+
}
+42
src/main/java/com/ezylang/evalex/functions/basic/FactFunction.java
+42
src/main/java/com/ezylang/evalex/functions/basic/FactFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.basic;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
import java.math.BigDecimal;
24
+
25
+
/** Factorial function, calculates the factorial of a base value. */
26
+
@FunctionParameter(name = "base")
27
+
public class FactFunction extends AbstractFunction {
28
+
29
+
@Override
30
+
public EvaluationValue evaluate(
31
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
32
+
int number = parameterValues[0].getNumberValue().intValue();
33
+
BigDecimal factorial = BigDecimal.ONE;
34
+
for (int i = 1; i <= number; i++) {
35
+
factorial =
36
+
factorial.multiply(
37
+
new BigDecimal(i, expression.getConfiguration().getMathContext()),
38
+
expression.getConfiguration().getMathContext());
39
+
}
40
+
return new EvaluationValue(factorial);
41
+
}
42
+
}
+36
src/main/java/com/ezylang/evalex/functions/basic/FloorFunction.java
+36
src/main/java/com/ezylang/evalex/functions/basic/FloorFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.basic;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
import java.math.RoundingMode;
24
+
25
+
/** Rounds the given value an integer using the rounding mode {@link RoundingMode#FLOOR} */
26
+
@FunctionParameter(name = "value")
27
+
public class FloorFunction extends AbstractFunction {
28
+
@Override
29
+
public EvaluationValue evaluate(
30
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
31
+
32
+
EvaluationValue value = parameterValues[0];
33
+
34
+
return new EvaluationValue(value.getNumberValue().setScale(0, RoundingMode.FLOOR));
35
+
}
36
+
}
+45
src/main/java/com/ezylang/evalex/functions/basic/IfFunction.java
+45
src/main/java/com/ezylang/evalex/functions/basic/IfFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.basic;
17
+
18
+
import com.ezylang.evalex.EvaluationException;
19
+
import com.ezylang.evalex.Expression;
20
+
import com.ezylang.evalex.data.EvaluationValue;
21
+
import com.ezylang.evalex.functions.AbstractFunction;
22
+
import com.ezylang.evalex.functions.FunctionParameter;
23
+
import com.ezylang.evalex.parser.Token;
24
+
25
+
/**
26
+
* Conditional evaluation function. If parameter <code>condition</code> is <code>true</code>, the
27
+
* <code>resultIfTrue</code> value is returned, else the <code>resultIfFalse</code> value. <code>
28
+
* resultIfTrue</code> and <code>resultIfFalse</code> are only evaluated (lazily evaluated),
29
+
* <b>after</b> the condition was evaluated.
30
+
*/
31
+
@FunctionParameter(name = "condition")
32
+
@FunctionParameter(name = "resultIfTrue", isLazy = true)
33
+
@FunctionParameter(name = "resultIfFalse", isLazy = true)
34
+
public class IfFunction extends AbstractFunction {
35
+
@Override
36
+
public EvaluationValue evaluate(
37
+
Expression expression, Token functionToken, EvaluationValue... parameterValues)
38
+
throws EvaluationException {
39
+
if (Boolean.TRUE.equals(parameterValues[0].getBooleanValue())) {
40
+
return expression.evaluateSubtree(parameterValues[1].getExpressionNode());
41
+
} else {
42
+
return expression.evaluateSubtree(parameterValues[2].getExpressionNode());
43
+
}
44
+
}
45
+
}
+35
src/main/java/com/ezylang/evalex/functions/basic/Log10Function.java
+35
src/main/java/com/ezylang/evalex/functions/basic/Log10Function.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.basic;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** The base 10 logarithm of a value */
25
+
@FunctionParameter(name = "value", nonZero = true, nonNegative = true)
26
+
public class Log10Function extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
double d = parameterValues[0].getNumberValue().doubleValue();
32
+
33
+
return expression.convertDoubleValue(Math.log10(d));
34
+
}
35
+
}
+35
src/main/java/com/ezylang/evalex/functions/basic/LogFunction.java
+35
src/main/java/com/ezylang/evalex/functions/basic/LogFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.basic;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** The natural logarithm (base e) of a value */
25
+
@FunctionParameter(name = "value", nonZero = true, nonNegative = true)
26
+
public class LogFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
double d = parameterValues[0].getNumberValue().doubleValue();
32
+
33
+
return expression.convertDoubleValue(Math.log(d));
34
+
}
35
+
}
+39
src/main/java/com/ezylang/evalex/functions/basic/MaxFunction.java
+39
src/main/java/com/ezylang/evalex/functions/basic/MaxFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.basic;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
import java.math.BigDecimal;
24
+
25
+
/** Returns the maximum value of all parameters. */
26
+
@FunctionParameter(name = "value", isVarArg = true)
27
+
public class MaxFunction extends AbstractFunction {
28
+
@Override
29
+
public EvaluationValue evaluate(
30
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
31
+
BigDecimal max = null;
32
+
for (EvaluationValue parameter : parameterValues) {
33
+
if (max == null || parameter.getNumberValue().compareTo(max) > 0) {
34
+
max = parameter.getNumberValue();
35
+
}
36
+
}
37
+
return new EvaluationValue(max);
38
+
}
39
+
}
+39
src/main/java/com/ezylang/evalex/functions/basic/MinFunction.java
+39
src/main/java/com/ezylang/evalex/functions/basic/MinFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.basic;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
import java.math.BigDecimal;
24
+
25
+
/** Returns the minimum value of all parameters. */
26
+
@FunctionParameter(name = "value", isVarArg = true)
27
+
public class MinFunction extends AbstractFunction {
28
+
@Override
29
+
public EvaluationValue evaluate(
30
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
31
+
BigDecimal min = null;
32
+
for (EvaluationValue parameter : parameterValues) {
33
+
if (min == null || parameter.getNumberValue().compareTo(min) < 0) {
34
+
min = parameter.getNumberValue();
35
+
}
36
+
}
37
+
return new EvaluationValue(min);
38
+
}
39
+
}
+34
src/main/java/com/ezylang/evalex/functions/basic/NotFunction.java
+34
src/main/java/com/ezylang/evalex/functions/basic/NotFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.basic;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Boolean negation. */
25
+
@FunctionParameter(name = "value")
26
+
public class NotFunction extends AbstractFunction {
27
+
28
+
@Override
29
+
public EvaluationValue evaluate(
30
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
31
+
32
+
return new EvaluationValue(!parameterValues[0].getBooleanValue());
33
+
}
34
+
}
+35
src/main/java/com/ezylang/evalex/functions/basic/RandomFunction.java
+35
src/main/java/com/ezylang/evalex/functions/basic/RandomFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.basic;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.parser.Token;
22
+
import java.security.SecureRandom;
23
+
24
+
/** Random function produces a random value between 0 and 1. */
25
+
public class RandomFunction extends AbstractFunction {
26
+
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
SecureRandom secureRandom = new SecureRandom();
32
+
33
+
return expression.convertDoubleValue(secureRandom.nextDouble());
34
+
}
35
+
}
+45
src/main/java/com/ezylang/evalex/functions/basic/RoundFunction.java
+45
src/main/java/com/ezylang/evalex/functions/basic/RoundFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.basic;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/**
25
+
* Rounds the given value to the specified scale, using the {@link java.math.MathContext} of the
26
+
* expression configuration.
27
+
*/
28
+
@FunctionParameter(name = "value")
29
+
@FunctionParameter(name = "scale")
30
+
public class RoundFunction extends AbstractFunction {
31
+
@Override
32
+
public EvaluationValue evaluate(
33
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
34
+
35
+
EvaluationValue value = parameterValues[0];
36
+
EvaluationValue precision = parameterValues[1];
37
+
38
+
return new EvaluationValue(
39
+
value
40
+
.getNumberValue()
41
+
.setScale(
42
+
precision.getNumberValue().intValue(),
43
+
expression.getConfiguration().getMathContext().getRoundingMode()));
44
+
}
45
+
}
+61
src/main/java/com/ezylang/evalex/functions/basic/SqrtFunction.java
+61
src/main/java/com/ezylang/evalex/functions/basic/SqrtFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.basic;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
import java.math.BigDecimal;
24
+
import java.math.BigInteger;
25
+
import java.math.MathContext;
26
+
27
+
/** Square root function, uses the standard {@link BigDecimal#sqrt(MathContext)} implementation. */
28
+
@FunctionParameter(name = "value", nonNegative = true)
29
+
public class SqrtFunction extends AbstractFunction {
30
+
31
+
@Override
32
+
public EvaluationValue evaluate(
33
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
34
+
35
+
/*
36
+
* From The Java Programmers Guide To numerical Computing
37
+
* (Ronald Mak, 2003)
38
+
*/
39
+
BigDecimal x = parameterValues[0].getNumberValue();
40
+
MathContext mathContext = expression.getConfiguration().getMathContext();
41
+
42
+
if (x.compareTo(BigDecimal.ZERO) == 0) {
43
+
return new EvaluationValue(BigDecimal.ZERO);
44
+
}
45
+
BigInteger n = x.movePointRight(mathContext.getPrecision() << 1).toBigInteger();
46
+
47
+
int bits = (n.bitLength() + 1) >> 1;
48
+
BigInteger ix = n.shiftRight(bits);
49
+
BigInteger ixPrev;
50
+
BigInteger test;
51
+
do {
52
+
ixPrev = ix;
53
+
ix = ix.add(n.divide(ix)).shiftRight(1);
54
+
// Give other threads a chance to work
55
+
Thread.yield();
56
+
test = ix.subtract(ixPrev).abs();
57
+
} while (test.compareTo(BigInteger.ZERO) != 0 && test.compareTo(BigInteger.ONE) != 0);
58
+
59
+
return new EvaluationValue(new BigDecimal(ix, mathContext.getPrecision()));
60
+
}
61
+
}
+37
src/main/java/com/ezylang/evalex/functions/basic/SumFunction.java
+37
src/main/java/com/ezylang/evalex/functions/basic/SumFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.basic;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
import java.math.BigDecimal;
24
+
25
+
/** Returns the sum value of all parameters. */
26
+
@FunctionParameter(name = "value", isVarArg = true)
27
+
public class SumFunction extends AbstractFunction {
28
+
@Override
29
+
public EvaluationValue evaluate(
30
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
31
+
BigDecimal sum = BigDecimal.ZERO;
32
+
for (EvaluationValue parameter : parameterValues) {
33
+
sum = sum.add(parameter.getNumberValue(), expression.getConfiguration().getMathContext());
34
+
}
35
+
return new EvaluationValue(sum);
36
+
}
37
+
}
+35
src/main/java/com/ezylang/evalex/functions/string/StringContains.java
+35
src/main/java/com/ezylang/evalex/functions/string/StringContains.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.string;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns true, if the string contains the substring (case-insensitive). */
25
+
@FunctionParameter(name = "string")
26
+
@FunctionParameter(name = "substring")
27
+
public class StringContains extends AbstractFunction {
28
+
@Override
29
+
public EvaluationValue evaluate(
30
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
31
+
String string = parameterValues[0].getStringValue();
32
+
String substring = parameterValues[1].getStringValue();
33
+
return new EvaluationValue(string.toUpperCase().contains(substring.toUpperCase()));
34
+
}
35
+
}
+32
src/main/java/com/ezylang/evalex/functions/string/StringLowerFunction.java
+32
src/main/java/com/ezylang/evalex/functions/string/StringLowerFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.string;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Converts the given value to lower case. */
25
+
@FunctionParameter(name = "value")
26
+
public class StringLowerFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
return new EvaluationValue(parameterValues[0].getStringValue().toLowerCase());
31
+
}
32
+
}
+32
src/main/java/com/ezylang/evalex/functions/string/StringUpperFunction.java
+32
src/main/java/com/ezylang/evalex/functions/string/StringUpperFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.string;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Converts the given value to upper case. */
25
+
@FunctionParameter(name = "value")
26
+
public class StringUpperFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
return new EvaluationValue(parameterValues[0].getStringValue().toUpperCase());
31
+
}
32
+
}
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/AcosFunction.java
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/AcosFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the arc-cosine (in degrees). */
25
+
@FunctionParameter(name = "value")
26
+
public class AcosFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
return expression.convertDoubleValue(
32
+
Math.toDegrees(Math.acos(parameterValues[0].getNumberValue().doubleValue())));
33
+
}
34
+
}
+40
src/main/java/com/ezylang/evalex/functions/trigonometric/AcosHFunction.java
+40
src/main/java/com/ezylang/evalex/functions/trigonometric/AcosHFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.EvaluationException;
19
+
import com.ezylang.evalex.Expression;
20
+
import com.ezylang.evalex.data.EvaluationValue;
21
+
import com.ezylang.evalex.functions.AbstractFunction;
22
+
import com.ezylang.evalex.functions.FunctionParameter;
23
+
import com.ezylang.evalex.parser.Token;
24
+
25
+
/** Returns the hyperbolic arc-cosine (in degrees). */
26
+
@FunctionParameter(name = "value")
27
+
public class AcosHFunction extends AbstractFunction {
28
+
@Override
29
+
public EvaluationValue evaluate(
30
+
Expression expression, Token functionToken, EvaluationValue... parameterValues)
31
+
throws EvaluationException {
32
+
33
+
/* Formula: acosh(x) = ln(x + sqrt(x^2 - 1)) */
34
+
double value = parameterValues[0].getNumberValue().doubleValue();
35
+
if (Double.compare(value, 1) < 0) {
36
+
throw new EvaluationException(functionToken, "Value must be greater or equal to one");
37
+
}
38
+
return expression.convertDoubleValue(Math.log(value + (Math.sqrt(Math.pow(value, 2) - 1))));
39
+
}
40
+
}
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/AcosRFunction.java
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/AcosRFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the arc-cosine (in radians). */
25
+
@FunctionParameter(name = "cosine")
26
+
public class AcosRFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
return expression.convertDoubleValue(
32
+
Math.acos(parameterValues[0].getNumberValue().doubleValue()));
33
+
}
34
+
}
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/AcotFunction.java
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/AcotFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the arc-co-tangent (in degrees). */
25
+
@FunctionParameter(name = "value", nonZero = true)
26
+
public class AcotFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
/* Formula: acot(x) = atan(1/x) */
32
+
return expression.convertDoubleValue(
33
+
Math.toDegrees(Math.atan(1 / parameterValues[0].getNumberValue().doubleValue())));
34
+
}
35
+
}
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/AcotRFunction.java
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/AcotRFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the arc-co-tangent (in radians). */
25
+
@FunctionParameter(name = "value", nonZero = true)
26
+
public class AcotRFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
/* Formula: acot(x) = atan(1/x) */
32
+
return expression.convertDoubleValue(
33
+
Math.atan(1 / parameterValues[0].getNumberValue().doubleValue()));
34
+
}
35
+
}
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/AsinFunction.java
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/AsinFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the arc-sine (in degrees). */
25
+
@FunctionParameter(name = "value")
26
+
public class AsinFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
return expression.convertDoubleValue(
32
+
Math.toDegrees(Math.asin(parameterValues[0].getNumberValue().doubleValue())));
33
+
}
34
+
}
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/AsinHFunction.java
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/AsinHFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the hyperbolic arc-sine (in degrees). */
25
+
@FunctionParameter(name = "value")
26
+
public class AsinHFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
/* Formula: asinh(x) = ln(x + sqrt(x^2 + 1)) */
32
+
double value = parameterValues[0].getNumberValue().doubleValue();
33
+
return expression.convertDoubleValue(Math.log(value + (Math.sqrt(Math.pow(value, 2) + 1))));
34
+
}
35
+
}
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/AsinRFunction.java
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/AsinRFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the arc-sine (in radians). */
25
+
@FunctionParameter(name = "value")
26
+
public class AsinRFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
return expression.convertDoubleValue(
32
+
Math.asin(parameterValues[0].getNumberValue().doubleValue()));
33
+
}
34
+
}
+38
src/main/java/com/ezylang/evalex/functions/trigonometric/Atan2Function.java
+38
src/main/java/com/ezylang/evalex/functions/trigonometric/Atan2Function.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the angle of atan2 (in degrees). */
25
+
@FunctionParameter(name = "y")
26
+
@FunctionParameter(name = "x")
27
+
public class Atan2Function extends AbstractFunction {
28
+
@Override
29
+
public EvaluationValue evaluate(
30
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
31
+
32
+
return expression.convertDoubleValue(
33
+
Math.toDegrees(
34
+
Math.atan2(
35
+
parameterValues[0].getNumberValue().doubleValue(),
36
+
parameterValues[1].getNumberValue().doubleValue())));
37
+
}
38
+
}
+37
src/main/java/com/ezylang/evalex/functions/trigonometric/Atan2RFunction.java
+37
src/main/java/com/ezylang/evalex/functions/trigonometric/Atan2RFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the angle of atan2 (in radians). */
25
+
@FunctionParameter(name = "y")
26
+
@FunctionParameter(name = "x")
27
+
public class Atan2RFunction extends AbstractFunction {
28
+
@Override
29
+
public EvaluationValue evaluate(
30
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
31
+
32
+
return expression.convertDoubleValue(
33
+
Math.atan2(
34
+
parameterValues[0].getNumberValue().doubleValue(),
35
+
parameterValues[1].getNumberValue().doubleValue()));
36
+
}
37
+
}
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/AtanFunction.java
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/AtanFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the arc-tangent (in degrees). */
25
+
@FunctionParameter(name = "value")
26
+
public class AtanFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
return expression.convertDoubleValue(
32
+
Math.toDegrees(Math.atan(parameterValues[0].getNumberValue().doubleValue())));
33
+
}
34
+
}
+40
src/main/java/com/ezylang/evalex/functions/trigonometric/AtanHFunction.java
+40
src/main/java/com/ezylang/evalex/functions/trigonometric/AtanHFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.EvaluationException;
19
+
import com.ezylang.evalex.Expression;
20
+
import com.ezylang.evalex.data.EvaluationValue;
21
+
import com.ezylang.evalex.functions.AbstractFunction;
22
+
import com.ezylang.evalex.functions.FunctionParameter;
23
+
import com.ezylang.evalex.parser.Token;
24
+
25
+
/** Returns the hyperbolic arc-sine (in degrees). */
26
+
@FunctionParameter(name = "value")
27
+
public class AtanHFunction extends AbstractFunction {
28
+
@Override
29
+
public EvaluationValue evaluate(
30
+
Expression expression, Token functionToken, EvaluationValue... parameterValues)
31
+
throws EvaluationException {
32
+
33
+
/* Formula: atanh(x) = 0.5*ln((1 + x)/(1 - x)) */
34
+
double value = parameterValues[0].getNumberValue().doubleValue();
35
+
if (Math.abs(value) >= 1) {
36
+
throw new EvaluationException(functionToken, "Absolute value must be less than 1");
37
+
}
38
+
return expression.convertDoubleValue(0.5 * Math.log((1 + value) / (1 - value)));
39
+
}
40
+
}
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/AtanRFunction.java
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/AtanRFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the arc-tangent (in radians). */
25
+
@FunctionParameter(name = "value")
26
+
public class AtanRFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
return expression.convertDoubleValue(
32
+
Math.atan(parameterValues[0].getNumberValue().doubleValue()));
33
+
}
34
+
}
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/CosFunction.java
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/CosFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the trigonometric cosine of an angle (in degrees). */
25
+
@FunctionParameter(name = "value")
26
+
public class CosFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
return expression.convertDoubleValue(
32
+
Math.cos(Math.toRadians(parameterValues[0].getNumberValue().doubleValue())));
33
+
}
34
+
}
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/CosHFunction.java
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/CosHFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the hyperbolic cosine of a value. */
25
+
@FunctionParameter(name = "value")
26
+
public class CosHFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
return expression.convertDoubleValue(
32
+
Math.cosh(parameterValues[0].getNumberValue().doubleValue()));
33
+
}
34
+
}
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/CosRFunction.java
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/CosRFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the trigonometric cosine of an angle (in radians). */
25
+
@FunctionParameter(name = "value")
26
+
public class CosRFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
return expression.convertDoubleValue(
32
+
Math.cos(parameterValues[0].getNumberValue().doubleValue()));
33
+
}
34
+
}
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/CotFunction.java
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/CotFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the co-tangent of an angle (in degrees). */
25
+
@FunctionParameter(name = "value", nonZero = true)
26
+
public class CotFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
/* Formula: cot(x) = cos(x) / sin(x) = 1 / tan(x) */
32
+
return expression.convertDoubleValue(
33
+
1 / Math.tan(Math.toRadians(parameterValues[0].getNumberValue().doubleValue())));
34
+
}
35
+
}
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/CotHFunction.java
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/CotHFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the hyperbolic co-tangent of a value. */
25
+
@FunctionParameter(name = "value", nonZero = true)
26
+
public class CotHFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
/* Formula: coth(x) = 1 / tanh(x) */
32
+
return expression.convertDoubleValue(
33
+
1 / Math.tanh(parameterValues[0].getNumberValue().doubleValue()));
34
+
}
35
+
}
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/CotRFunction.java
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/CotRFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the trigonometric co-tangent of an angle (in radians). */
25
+
@FunctionParameter(name = "value", nonZero = true)
26
+
public class CotRFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
/* Formula: cot(x) = cos(x) / sin(x) = 1 / tan(x) */
32
+
return expression.convertDoubleValue(
33
+
1 / Math.tan(parameterValues[0].getNumberValue().doubleValue()));
34
+
}
35
+
}
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/CscFunction.java
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/CscFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the co-secant (in degrees). */
25
+
@FunctionParameter(name = "value", nonZero = true)
26
+
public class CscFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
/* Formula: csc(x) = 1 / sin(x) */
32
+
return expression.convertDoubleValue(
33
+
1 / Math.sin(Math.toRadians(parameterValues[0].getNumberValue().doubleValue())));
34
+
}
35
+
}
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/CscHFunction.java
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/CscHFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the co-secant (in degrees). */
25
+
@FunctionParameter(name = "value", nonZero = true)
26
+
public class CscHFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
/* Formula: csch(x) = 1 / sinh(x) */
32
+
return expression.convertDoubleValue(
33
+
1 / Math.sinh(parameterValues[0].getNumberValue().doubleValue()));
34
+
}
35
+
}
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/CscRFunction.java
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/CscRFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the co-secant (in radians). */
25
+
@FunctionParameter(name = "value", nonZero = true)
26
+
public class CscRFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
/* Formula: csc(x) = 1 / sin(x) */
32
+
return expression.convertDoubleValue(
33
+
1 / Math.sin(parameterValues[0].getNumberValue().doubleValue()));
34
+
}
35
+
}
+37
src/main/java/com/ezylang/evalex/functions/trigonometric/DegFunction.java
+37
src/main/java/com/ezylang/evalex/functions/trigonometric/DegFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/**
25
+
* Converts an angle measured in radians to an approximately equivalent angle measured in degrees.
26
+
*/
27
+
@FunctionParameter(name = "radians")
28
+
public class DegFunction extends AbstractFunction {
29
+
@Override
30
+
public EvaluationValue evaluate(
31
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
32
+
33
+
double rad = Math.toDegrees(parameterValues[0].getNumberValue().doubleValue());
34
+
35
+
return expression.convertDoubleValue(rad);
36
+
}
37
+
}
+37
src/main/java/com/ezylang/evalex/functions/trigonometric/RadFunction.java
+37
src/main/java/com/ezylang/evalex/functions/trigonometric/RadFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/**
25
+
* Converts an angle measured in degrees to an approximately equivalent angle measured in radians.
26
+
*/
27
+
@FunctionParameter(name = "degrees")
28
+
public class RadFunction extends AbstractFunction {
29
+
@Override
30
+
public EvaluationValue evaluate(
31
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
32
+
33
+
double deg = Math.toRadians(parameterValues[0].getNumberValue().doubleValue());
34
+
35
+
return expression.convertDoubleValue(deg);
36
+
}
37
+
}
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/SecFunction.java
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/SecFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the secant (in degrees). */
25
+
@FunctionParameter(name = "value", nonZero = true)
26
+
public class SecFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
/* Formula: sec(x) = 1 / cos(x) */
32
+
return expression.convertDoubleValue(
33
+
1 / Math.cos(Math.toRadians(parameterValues[0].getNumberValue().doubleValue())));
34
+
}
35
+
}
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/SecHFunction.java
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/SecHFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the hyperbolic secant (in degrees). */
25
+
@FunctionParameter(name = "value", nonZero = true)
26
+
public class SecHFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
/* Formula: sech(x) = 1 / cosh(x) */
32
+
return expression.convertDoubleValue(
33
+
1 / Math.cosh(parameterValues[0].getNumberValue().doubleValue()));
34
+
}
35
+
}
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/SecRFunction.java
+35
src/main/java/com/ezylang/evalex/functions/trigonometric/SecRFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the secant (in radians). */
25
+
@FunctionParameter(name = "value", nonZero = true)
26
+
public class SecRFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
/* Formula: sec(x) = 1 / cos(x) */
32
+
return expression.convertDoubleValue(
33
+
1 / Math.cos(parameterValues[0].getNumberValue().doubleValue()));
34
+
}
35
+
}
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/SinFunction.java
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/SinFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the trigonometric sine of an angle (in degrees). */
25
+
@FunctionParameter(name = "value")
26
+
public class SinFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
return expression.convertDoubleValue(
32
+
Math.sin(Math.toRadians(parameterValues[0].getNumberValue().doubleValue())));
33
+
}
34
+
}
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/SinHFunction.java
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/SinHFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the hyperbolic sine of a value. */
25
+
@FunctionParameter(name = "value")
26
+
public class SinHFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
return expression.convertDoubleValue(
32
+
Math.sinh(parameterValues[0].getNumberValue().doubleValue()));
33
+
}
34
+
}
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/SinRFunction.java
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/SinRFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the trigonometric sine of an angle (in radians). */
25
+
@FunctionParameter(name = "value")
26
+
public class SinRFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
return expression.convertDoubleValue(
32
+
Math.sin(parameterValues[0].getNumberValue().doubleValue()));
33
+
}
34
+
}
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/TanFunction.java
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/TanFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the trigonometric tangent of an angle (in degrees). */
25
+
@FunctionParameter(name = "value")
26
+
public class TanFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
return expression.convertDoubleValue(
32
+
Math.tan(Math.toRadians(parameterValues[0].getNumberValue().doubleValue())));
33
+
}
34
+
}
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/TanHFunction.java
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/TanHFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the hyperbolic tangent of a value. */
25
+
@FunctionParameter(name = "value")
26
+
public class TanHFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
return expression.convertDoubleValue(
32
+
Math.tanh(parameterValues[0].getNumberValue().doubleValue()));
33
+
}
34
+
}
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/TanRFunction.java
+34
src/main/java/com/ezylang/evalex/functions/trigonometric/TanRFunction.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Returns the trigonometric tangent of an angle (in radians). */
25
+
@FunctionParameter(name = "value")
26
+
public class TanRFunction extends AbstractFunction {
27
+
@Override
28
+
public EvaluationValue evaluate(
29
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
30
+
31
+
return expression.convertDoubleValue(
32
+
Math.tan(parameterValues[0].getNumberValue().doubleValue()));
33
+
}
34
+
}
+84
src/main/java/com/ezylang/evalex/operators/AbstractOperator.java
+84
src/main/java/com/ezylang/evalex/operators/AbstractOperator.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators;
17
+
18
+
import static com.ezylang.evalex.operators.OperatorIfc.OperatorType.PREFIX_OPERATOR;
19
+
20
+
import com.ezylang.evalex.config.ExpressionConfiguration;
21
+
import lombok.Getter;
22
+
23
+
/**
24
+
* Abstract implementation of the {@link OperatorIfc}, used as base class for operator
25
+
* implementations.
26
+
*/
27
+
public abstract class AbstractOperator implements OperatorIfc {
28
+
29
+
@Getter private final int precedence;
30
+
31
+
private final boolean leftAssociative;
32
+
33
+
OperatorType type;
34
+
35
+
/**
36
+
* Creates a new operator and uses the {@link InfixOperator} annotation to create the operator
37
+
* definition.
38
+
*/
39
+
protected AbstractOperator() {
40
+
InfixOperator infixAnnotation = getClass().getAnnotation(InfixOperator.class);
41
+
PrefixOperator prefixAnnotation = getClass().getAnnotation(PrefixOperator.class);
42
+
PostfixOperator postfixAnnotation = getClass().getAnnotation(PostfixOperator.class);
43
+
if (infixAnnotation != null) {
44
+
this.type = OperatorType.INFIX_OPERATOR;
45
+
this.precedence = infixAnnotation.precedence();
46
+
this.leftAssociative = infixAnnotation.leftAssociative();
47
+
} else if (prefixAnnotation != null) {
48
+
this.type = PREFIX_OPERATOR;
49
+
this.precedence = prefixAnnotation.precedence();
50
+
this.leftAssociative = prefixAnnotation.leftAssociative();
51
+
} else if (postfixAnnotation != null) {
52
+
this.type = OperatorType.POSTFIX_OPERATOR;
53
+
this.precedence = postfixAnnotation.precedence();
54
+
this.leftAssociative = postfixAnnotation.leftAssociative();
55
+
} else {
56
+
throw new OperatorAnnotationNotFoundException(this.getClass().getName());
57
+
}
58
+
}
59
+
60
+
@Override
61
+
public int getPrecedence(ExpressionConfiguration configuration) {
62
+
return getPrecedence();
63
+
}
64
+
65
+
@Override
66
+
public boolean isLeftAssociative() {
67
+
return leftAssociative;
68
+
}
69
+
70
+
@Override
71
+
public boolean isPrefix() {
72
+
return type == PREFIX_OPERATOR;
73
+
}
74
+
75
+
@Override
76
+
public boolean isPostfix() {
77
+
return type == OperatorType.POSTFIX_OPERATOR;
78
+
}
79
+
80
+
@Override
81
+
public boolean isInfix() {
82
+
return type == OperatorType.INFIX_OPERATOR;
83
+
}
84
+
}
+35
src/main/java/com/ezylang/evalex/operators/InfixOperator.java
+35
src/main/java/com/ezylang/evalex/operators/InfixOperator.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators;
17
+
18
+
import java.lang.annotation.Documented;
19
+
import java.lang.annotation.ElementType;
20
+
import java.lang.annotation.Retention;
21
+
import java.lang.annotation.RetentionPolicy;
22
+
import java.lang.annotation.Target;
23
+
24
+
/** The infix operator annotation */
25
+
@Documented
26
+
@Target(ElementType.TYPE)
27
+
@Retention(RetentionPolicy.RUNTIME)
28
+
public @interface InfixOperator {
29
+
30
+
/** Operator precedence, usually one from the constants in {@link OperatorIfc}. */
31
+
int precedence();
32
+
33
+
/** Operator associativity, defaults to <code>true</code>. */
34
+
boolean leftAssociative() default true;
35
+
}
+27
src/main/java/com/ezylang/evalex/operators/OperatorAnnotationNotFoundException.java
+27
src/main/java/com/ezylang/evalex/operators/OperatorAnnotationNotFoundException.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators;
17
+
18
+
/**
19
+
* Operator properties are defined through a class annotation, this exception is thrown if no
20
+
* annotation was found when creating the operator instance.
21
+
*/
22
+
public class OperatorAnnotationNotFoundException extends RuntimeException {
23
+
24
+
public OperatorAnnotationNotFoundException(String className) {
25
+
super("Operator annotation for '" + className + "' not found");
26
+
}
27
+
}
+126
src/main/java/com/ezylang/evalex/operators/OperatorIfc.java
+126
src/main/java/com/ezylang/evalex/operators/OperatorIfc.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators;
17
+
18
+
import com.ezylang.evalex.EvaluationException;
19
+
import com.ezylang.evalex.Expression;
20
+
import com.ezylang.evalex.config.ExpressionConfiguration;
21
+
import com.ezylang.evalex.data.EvaluationValue;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/**
25
+
* Interface that is required for all operators in an operator dictionary for evaluation of
26
+
* expressions. There are three operator type: prefix, postfix and infix. Every operator has a
27
+
* precedence, which defines the order of operator evaluation. The associativity of an operator is a
28
+
* property that determines how operators of the same precedence are grouped in the absence of
29
+
* parentheses.
30
+
*/
31
+
public interface OperatorIfc {
32
+
33
+
/** The operator type. */
34
+
enum OperatorType {
35
+
/** Unary prefix operator, like -x */
36
+
PREFIX_OPERATOR,
37
+
/** Unary postfix operator,like x! */
38
+
POSTFIX_OPERATOR,
39
+
/** Binary infix operator, like x+y */
40
+
INFIX_OPERATOR
41
+
}
42
+
43
+
/** Or operator precedence: || */
44
+
int OPERATOR_PRECEDENCE_OR = 2;
45
+
46
+
/** And operator precedence: && */
47
+
int OPERATOR_PRECEDENCE_AND = 4;
48
+
49
+
/** Equality operators precedence: =, ==, !=, <> */
50
+
int OPERATOR_PRECEDENCE_EQUALITY = 7;
51
+
52
+
/** Comparative operators precedence: <, >, <=, >= */
53
+
int OPERATOR_PRECEDENCE_COMPARISON = 10;
54
+
55
+
/** Additive operators precedence: + and - */
56
+
int OPERATOR_PRECEDENCE_ADDITIVE = 20;
57
+
58
+
/** Multiplicative operators precedence: *, /, % */
59
+
int OPERATOR_PRECEDENCE_MULTIPLICATIVE = 30;
60
+
61
+
/** Power operator precedence: ^ */
62
+
int OPERATOR_PRECEDENCE_POWER = 40;
63
+
64
+
/** Unary operators precedence: + and - as prefix */
65
+
int OPERATOR_PRECEDENCE_UNARY = 60;
66
+
67
+
/**
68
+
* An optional higher power operator precedence, higher than the unary prefix, e.g. -2^2 equals to
69
+
* 4 or -4, depending on precedence configuration.
70
+
*/
71
+
int OPERATOR_PRECEDENCE_POWER_HIGHER = 80;
72
+
73
+
/**
74
+
* @return The operator's precedence.
75
+
*/
76
+
int getPrecedence();
77
+
78
+
/**
79
+
* If operators with same precedence are evaluated from left to right.
80
+
*
81
+
* @return The associativity.
82
+
*/
83
+
boolean isLeftAssociative();
84
+
85
+
/**
86
+
* If it is a prefix operator.
87
+
*
88
+
* @return <code>true</code> if it is a prefix operator.
89
+
*/
90
+
boolean isPrefix();
91
+
92
+
/**
93
+
* If it is a postfix operator.
94
+
*
95
+
* @return <code>true</code> if it is a postfix operator.
96
+
*/
97
+
boolean isPostfix();
98
+
99
+
/**
100
+
* If it is an infix operator.
101
+
*
102
+
* @return <code>true</code> if it is an infix operator.
103
+
*/
104
+
boolean isInfix();
105
+
106
+
/**
107
+
* Called during parsing, can be implemented to return a customized precedence.
108
+
*
109
+
* @param configuration The expression configuration.
110
+
* @return The default precedence from the operator annotation, or a customized value.
111
+
*/
112
+
int getPrecedence(ExpressionConfiguration configuration);
113
+
114
+
/**
115
+
* Performs the operator logic and returns an evaluation result.
116
+
*
117
+
* @param expression The expression, where this function is executed. Can be used to access the
118
+
* expression configuration.
119
+
* @param operatorToken The operator token from the parsed expression.
120
+
* @param operands The operands, one for prefix and postfix operators, two for infix operators.
121
+
* @return The evaluation result in form of a {@link EvaluationValue}.
122
+
* @throws EvaluationException In case there were problems during evaluation.
123
+
*/
124
+
EvaluationValue evaluate(Expression expression, Token operatorToken, EvaluationValue... operands)
125
+
throws EvaluationException;
126
+
}
+37
src/main/java/com/ezylang/evalex/operators/PostfixOperator.java
+37
src/main/java/com/ezylang/evalex/operators/PostfixOperator.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators;
17
+
18
+
import static com.ezylang.evalex.operators.OperatorIfc.OPERATOR_PRECEDENCE_UNARY;
19
+
20
+
import java.lang.annotation.Documented;
21
+
import java.lang.annotation.ElementType;
22
+
import java.lang.annotation.Retention;
23
+
import java.lang.annotation.RetentionPolicy;
24
+
import java.lang.annotation.Target;
25
+
26
+
/** The postfix operator annotation */
27
+
@Documented
28
+
@Target(ElementType.TYPE)
29
+
@Retention(RetentionPolicy.RUNTIME)
30
+
public @interface PostfixOperator {
31
+
32
+
/** Operator precedence, usually one from the constants in {@link OperatorIfc}. */
33
+
int precedence() default OPERATOR_PRECEDENCE_UNARY;
34
+
35
+
/** Operator associativity, defaults to <code>true</code>. */
36
+
boolean leftAssociative() default true;
37
+
}
+37
src/main/java/com/ezylang/evalex/operators/PrefixOperator.java
+37
src/main/java/com/ezylang/evalex/operators/PrefixOperator.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators;
17
+
18
+
import static com.ezylang.evalex.operators.OperatorIfc.OPERATOR_PRECEDENCE_UNARY;
19
+
20
+
import java.lang.annotation.Documented;
21
+
import java.lang.annotation.ElementType;
22
+
import java.lang.annotation.Retention;
23
+
import java.lang.annotation.RetentionPolicy;
24
+
import java.lang.annotation.Target;
25
+
26
+
/** The prefix operator annotation */
27
+
@Documented
28
+
@Target(ElementType.TYPE)
29
+
@Retention(RetentionPolicy.RUNTIME)
30
+
public @interface PrefixOperator {
31
+
32
+
/** Operator precedence, usually one from the constants in {@link OperatorIfc}. */
33
+
int precedence() default OPERATOR_PRECEDENCE_UNARY;
34
+
35
+
/** Operator associativity, defaults to <code>true</code>. */
36
+
boolean leftAssociative() default true;
37
+
}
+54
src/main/java/com/ezylang/evalex/operators/arithmetic/InfixDivisionOperator.java
+54
src/main/java/com/ezylang/evalex/operators/arithmetic/InfixDivisionOperator.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.arithmetic;
17
+
18
+
import static com.ezylang.evalex.operators.OperatorIfc.OPERATOR_PRECEDENCE_MULTIPLICATIVE;
19
+
20
+
import com.ezylang.evalex.EvaluationException;
21
+
import com.ezylang.evalex.Expression;
22
+
import com.ezylang.evalex.data.EvaluationValue;
23
+
import com.ezylang.evalex.operators.AbstractOperator;
24
+
import com.ezylang.evalex.operators.InfixOperator;
25
+
import com.ezylang.evalex.parser.Token;
26
+
import java.math.BigDecimal;
27
+
28
+
/** Division of two numbers. */
29
+
@InfixOperator(precedence = OPERATOR_PRECEDENCE_MULTIPLICATIVE)
30
+
public class InfixDivisionOperator extends AbstractOperator {
31
+
32
+
@Override
33
+
public EvaluationValue evaluate(
34
+
Expression expression, Token operatorToken, EvaluationValue... operands)
35
+
throws EvaluationException {
36
+
EvaluationValue leftOperand = operands[0];
37
+
EvaluationValue rightOperand = operands[1];
38
+
39
+
if (leftOperand.isNumberValue() && rightOperand.isNumberValue()) {
40
+
41
+
if (rightOperand.getNumberValue().equals(BigDecimal.ZERO)) {
42
+
throw new EvaluationException(operatorToken, "Division by zero");
43
+
}
44
+
45
+
return new EvaluationValue(
46
+
leftOperand
47
+
.getNumberValue()
48
+
.divide(
49
+
rightOperand.getNumberValue(), expression.getConfiguration().getMathContext()));
50
+
} else {
51
+
throw EvaluationException.ofUnsupportedDataTypeInOperation(operatorToken);
52
+
}
53
+
}
54
+
}
+48
src/main/java/com/ezylang/evalex/operators/arithmetic/InfixMinusOperator.java
+48
src/main/java/com/ezylang/evalex/operators/arithmetic/InfixMinusOperator.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.arithmetic;
17
+
18
+
import static com.ezylang.evalex.operators.OperatorIfc.OPERATOR_PRECEDENCE_ADDITIVE;
19
+
20
+
import com.ezylang.evalex.EvaluationException;
21
+
import com.ezylang.evalex.Expression;
22
+
import com.ezylang.evalex.data.EvaluationValue;
23
+
import com.ezylang.evalex.operators.AbstractOperator;
24
+
import com.ezylang.evalex.operators.InfixOperator;
25
+
import com.ezylang.evalex.parser.Token;
26
+
27
+
/** Subtraction of two numbers. */
28
+
@InfixOperator(precedence = OPERATOR_PRECEDENCE_ADDITIVE)
29
+
public class InfixMinusOperator extends AbstractOperator {
30
+
31
+
@Override
32
+
public EvaluationValue evaluate(
33
+
Expression expression, Token operatorToken, EvaluationValue... operands)
34
+
throws EvaluationException {
35
+
EvaluationValue leftOperand = operands[0];
36
+
EvaluationValue rightOperand = operands[1];
37
+
38
+
if (leftOperand.isNumberValue() && rightOperand.isNumberValue()) {
39
+
return new EvaluationValue(
40
+
leftOperand
41
+
.getNumberValue()
42
+
.subtract(
43
+
rightOperand.getNumberValue(), expression.getConfiguration().getMathContext()));
44
+
} else {
45
+
throw EvaluationException.ofUnsupportedDataTypeInOperation(operatorToken);
46
+
}
47
+
}
48
+
}
+54
src/main/java/com/ezylang/evalex/operators/arithmetic/InfixModuloOperator.java
+54
src/main/java/com/ezylang/evalex/operators/arithmetic/InfixModuloOperator.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.arithmetic;
17
+
18
+
import static com.ezylang.evalex.operators.OperatorIfc.OPERATOR_PRECEDENCE_MULTIPLICATIVE;
19
+
20
+
import com.ezylang.evalex.EvaluationException;
21
+
import com.ezylang.evalex.Expression;
22
+
import com.ezylang.evalex.data.EvaluationValue;
23
+
import com.ezylang.evalex.operators.AbstractOperator;
24
+
import com.ezylang.evalex.operators.InfixOperator;
25
+
import com.ezylang.evalex.parser.Token;
26
+
import java.math.BigDecimal;
27
+
28
+
/** Remainder (modulo) of two numbers. */
29
+
@InfixOperator(precedence = OPERATOR_PRECEDENCE_MULTIPLICATIVE)
30
+
public class InfixModuloOperator extends AbstractOperator {
31
+
32
+
@Override
33
+
public EvaluationValue evaluate(
34
+
Expression expression, Token operatorToken, EvaluationValue... operands)
35
+
throws EvaluationException {
36
+
EvaluationValue leftOperand = operands[0];
37
+
EvaluationValue rightOperand = operands[1];
38
+
39
+
if (leftOperand.isNumberValue() && rightOperand.isNumberValue()) {
40
+
41
+
if (rightOperand.getNumberValue().equals(BigDecimal.ZERO)) {
42
+
throw new EvaluationException(operatorToken, "Division by zero");
43
+
}
44
+
45
+
return new EvaluationValue(
46
+
leftOperand
47
+
.getNumberValue()
48
+
.remainder(
49
+
rightOperand.getNumberValue(), expression.getConfiguration().getMathContext()));
50
+
} else {
51
+
throw EvaluationException.ofUnsupportedDataTypeInOperation(operatorToken);
52
+
}
53
+
}
54
+
}
+48
src/main/java/com/ezylang/evalex/operators/arithmetic/InfixMultiplicationOperator.java
+48
src/main/java/com/ezylang/evalex/operators/arithmetic/InfixMultiplicationOperator.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.arithmetic;
17
+
18
+
import static com.ezylang.evalex.operators.OperatorIfc.OPERATOR_PRECEDENCE_MULTIPLICATIVE;
19
+
20
+
import com.ezylang.evalex.EvaluationException;
21
+
import com.ezylang.evalex.Expression;
22
+
import com.ezylang.evalex.data.EvaluationValue;
23
+
import com.ezylang.evalex.operators.AbstractOperator;
24
+
import com.ezylang.evalex.operators.InfixOperator;
25
+
import com.ezylang.evalex.parser.Token;
26
+
27
+
/** Multiplication of two numbers. */
28
+
@InfixOperator(precedence = OPERATOR_PRECEDENCE_MULTIPLICATIVE)
29
+
public class InfixMultiplicationOperator extends AbstractOperator {
30
+
31
+
@Override
32
+
public EvaluationValue evaluate(
33
+
Expression expression, Token operatorToken, EvaluationValue... operands)
34
+
throws EvaluationException {
35
+
EvaluationValue leftOperand = operands[0];
36
+
EvaluationValue rightOperand = operands[1];
37
+
38
+
if (leftOperand.isNumberValue() && rightOperand.isNumberValue()) {
39
+
return new EvaluationValue(
40
+
leftOperand
41
+
.getNumberValue()
42
+
.multiply(
43
+
rightOperand.getNumberValue(), expression.getConfiguration().getMathContext()));
44
+
} else {
45
+
throw EvaluationException.ofUnsupportedDataTypeInOperation(operatorToken);
46
+
}
47
+
}
48
+
}
+47
src/main/java/com/ezylang/evalex/operators/arithmetic/InfixPlusOperator.java
+47
src/main/java/com/ezylang/evalex/operators/arithmetic/InfixPlusOperator.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.arithmetic;
17
+
18
+
import static com.ezylang.evalex.operators.OperatorIfc.OPERATOR_PRECEDENCE_ADDITIVE;
19
+
20
+
import com.ezylang.evalex.Expression;
21
+
import com.ezylang.evalex.data.EvaluationValue;
22
+
import com.ezylang.evalex.operators.AbstractOperator;
23
+
import com.ezylang.evalex.operators.InfixOperator;
24
+
import com.ezylang.evalex.parser.Token;
25
+
26
+
/**
27
+
* Addition of numbers and strings. If one operand is a string, a string concatenation is performed.
28
+
*/
29
+
@InfixOperator(precedence = OPERATOR_PRECEDENCE_ADDITIVE)
30
+
public class InfixPlusOperator extends AbstractOperator {
31
+
32
+
@Override
33
+
public EvaluationValue evaluate(
34
+
Expression expression, Token operatorToken, EvaluationValue... operands) {
35
+
EvaluationValue leftOperand = operands[0];
36
+
EvaluationValue rightOperand = operands[1];
37
+
38
+
if (leftOperand.isNumberValue() && rightOperand.isNumberValue()) {
39
+
return new EvaluationValue(
40
+
leftOperand
41
+
.getNumberValue()
42
+
.add(rightOperand.getNumberValue(), expression.getConfiguration().getMathContext()));
43
+
} else {
44
+
return new EvaluationValue(leftOperand.getStringValue() + rightOperand.getStringValue());
45
+
}
46
+
}
47
+
}
+79
src/main/java/com/ezylang/evalex/operators/arithmetic/InfixPowerOfOperator.java
+79
src/main/java/com/ezylang/evalex/operators/arithmetic/InfixPowerOfOperator.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.arithmetic;
17
+
18
+
import static com.ezylang.evalex.operators.OperatorIfc.OPERATOR_PRECEDENCE_POWER;
19
+
20
+
import com.ezylang.evalex.EvaluationException;
21
+
import com.ezylang.evalex.Expression;
22
+
import com.ezylang.evalex.config.ExpressionConfiguration;
23
+
import com.ezylang.evalex.data.EvaluationValue;
24
+
import com.ezylang.evalex.operators.AbstractOperator;
25
+
import com.ezylang.evalex.operators.InfixOperator;
26
+
import com.ezylang.evalex.parser.Token;
27
+
import java.math.BigDecimal;
28
+
import java.math.MathContext;
29
+
import java.math.RoundingMode;
30
+
31
+
/**
32
+
* Power of operator, calculates the power of right operand of left operand. The precedence is read
33
+
* from the configuration during parsing.
34
+
*
35
+
* @see #getPrecedence(ExpressionConfiguration)
36
+
*/
37
+
@InfixOperator(precedence = OPERATOR_PRECEDENCE_POWER, leftAssociative = false)
38
+
public class InfixPowerOfOperator extends AbstractOperator {
39
+
40
+
@Override
41
+
public EvaluationValue evaluate(
42
+
Expression expression, Token operatorToken, EvaluationValue... operands)
43
+
throws EvaluationException {
44
+
EvaluationValue leftOperand = operands[0];
45
+
EvaluationValue rightOperand = operands[1];
46
+
47
+
if (leftOperand.isNumberValue() && rightOperand.isNumberValue()) {
48
+
/*-
49
+
* Thanks to Gene Marin:
50
+
* http://stackoverflow.com/questions/3579779/how-to-do-a-fractional-power-on-bigdecimal-in-java
51
+
*/
52
+
53
+
MathContext mathContext = expression.getConfiguration().getMathContext();
54
+
BigDecimal v1 = leftOperand.getNumberValue();
55
+
BigDecimal v2 = rightOperand.getNumberValue();
56
+
57
+
int signOf2 = v2.signum();
58
+
double dn1 = v1.doubleValue();
59
+
v2 = v2.multiply(new BigDecimal(signOf2)); // n2 is now positive
60
+
BigDecimal remainderOf2 = v2.remainder(BigDecimal.ONE);
61
+
BigDecimal n2IntPart = v2.subtract(remainderOf2);
62
+
BigDecimal intPow = v1.pow(n2IntPart.intValueExact(), mathContext);
63
+
BigDecimal doublePow = BigDecimal.valueOf(Math.pow(dn1, remainderOf2.doubleValue()));
64
+
65
+
BigDecimal result = intPow.multiply(doublePow, mathContext);
66
+
if (signOf2 == -1) {
67
+
result = BigDecimal.ONE.divide(result, mathContext.getPrecision(), RoundingMode.HALF_UP);
68
+
}
69
+
return new EvaluationValue(result);
70
+
} else {
71
+
throw EvaluationException.ofUnsupportedDataTypeInOperation(operatorToken);
72
+
}
73
+
}
74
+
75
+
@Override
76
+
public int getPrecedence(ExpressionConfiguration configuration) {
77
+
return configuration.getPowerOfPrecedence();
78
+
}
79
+
}
+42
src/main/java/com/ezylang/evalex/operators/arithmetic/PrefixMinusOperator.java
+42
src/main/java/com/ezylang/evalex/operators/arithmetic/PrefixMinusOperator.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.arithmetic;
17
+
18
+
import com.ezylang.evalex.EvaluationException;
19
+
import com.ezylang.evalex.Expression;
20
+
import com.ezylang.evalex.data.EvaluationValue;
21
+
import com.ezylang.evalex.operators.AbstractOperator;
22
+
import com.ezylang.evalex.operators.PrefixOperator;
23
+
import com.ezylang.evalex.parser.Token;
24
+
25
+
/** Unary prefix minus. */
26
+
@PrefixOperator(leftAssociative = false)
27
+
public class PrefixMinusOperator extends AbstractOperator {
28
+
29
+
@Override
30
+
public EvaluationValue evaluate(
31
+
Expression expression, Token operatorToken, EvaluationValue... operands)
32
+
throws EvaluationException {
33
+
EvaluationValue operand = operands[0];
34
+
35
+
if (operand.isNumberValue()) {
36
+
return new EvaluationValue(
37
+
operand.getNumberValue().negate(expression.getConfiguration().getMathContext()));
38
+
} else {
39
+
throw EvaluationException.ofUnsupportedDataTypeInOperation(operatorToken);
40
+
}
41
+
}
42
+
}
+42
src/main/java/com/ezylang/evalex/operators/arithmetic/PrefixPlusOperator.java
+42
src/main/java/com/ezylang/evalex/operators/arithmetic/PrefixPlusOperator.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.arithmetic;
17
+
18
+
import com.ezylang.evalex.EvaluationException;
19
+
import com.ezylang.evalex.Expression;
20
+
import com.ezylang.evalex.data.EvaluationValue;
21
+
import com.ezylang.evalex.operators.AbstractOperator;
22
+
import com.ezylang.evalex.operators.PrefixOperator;
23
+
import com.ezylang.evalex.parser.Token;
24
+
25
+
/** Unary prefix plus. */
26
+
@PrefixOperator(leftAssociative = false)
27
+
public class PrefixPlusOperator extends AbstractOperator {
28
+
29
+
@Override
30
+
public EvaluationValue evaluate(
31
+
Expression expression, Token operatorToken, EvaluationValue... operands)
32
+
throws EvaluationException {
33
+
EvaluationValue operator = operands[0];
34
+
35
+
if (operator.isNumberValue()) {
36
+
return new EvaluationValue(
37
+
operator.getNumberValue().plus(expression.getConfiguration().getMathContext()));
38
+
} else {
39
+
throw EvaluationException.ofUnsupportedDataTypeInOperation(operatorToken);
40
+
}
41
+
}
42
+
}
+35
src/main/java/com/ezylang/evalex/operators/booleans/InfixAndOperator.java
+35
src/main/java/com/ezylang/evalex/operators/booleans/InfixAndOperator.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.booleans;
17
+
18
+
import static com.ezylang.evalex.operators.OperatorIfc.OPERATOR_PRECEDENCE_AND;
19
+
20
+
import com.ezylang.evalex.Expression;
21
+
import com.ezylang.evalex.data.EvaluationValue;
22
+
import com.ezylang.evalex.operators.AbstractOperator;
23
+
import com.ezylang.evalex.operators.InfixOperator;
24
+
import com.ezylang.evalex.parser.Token;
25
+
26
+
/** Boolean AND of two values. */
27
+
@InfixOperator(precedence = OPERATOR_PRECEDENCE_AND)
28
+
public class InfixAndOperator extends AbstractOperator {
29
+
30
+
@Override
31
+
public EvaluationValue evaluate(
32
+
Expression expression, Token operatorToken, EvaluationValue... operands) {
33
+
return new EvaluationValue(operands[0].getBooleanValue() && operands[1].getBooleanValue());
34
+
}
35
+
}
+35
src/main/java/com/ezylang/evalex/operators/booleans/InfixEqualsOperator.java
+35
src/main/java/com/ezylang/evalex/operators/booleans/InfixEqualsOperator.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.booleans;
17
+
18
+
import static com.ezylang.evalex.operators.OperatorIfc.OPERATOR_PRECEDENCE_EQUALITY;
19
+
20
+
import com.ezylang.evalex.Expression;
21
+
import com.ezylang.evalex.data.EvaluationValue;
22
+
import com.ezylang.evalex.operators.AbstractOperator;
23
+
import com.ezylang.evalex.operators.InfixOperator;
24
+
import com.ezylang.evalex.parser.Token;
25
+
26
+
/** Equality of two values. */
27
+
@InfixOperator(precedence = OPERATOR_PRECEDENCE_EQUALITY)
28
+
public class InfixEqualsOperator extends AbstractOperator {
29
+
30
+
@Override
31
+
public EvaluationValue evaluate(
32
+
Expression expression, Token operatorToken, EvaluationValue... operands) {
33
+
return new EvaluationValue(operands[0].equals(operands[1]));
34
+
}
35
+
}
+35
src/main/java/com/ezylang/evalex/operators/booleans/InfixGreaterEqualsOperator.java
+35
src/main/java/com/ezylang/evalex/operators/booleans/InfixGreaterEqualsOperator.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.booleans;
17
+
18
+
import static com.ezylang.evalex.operators.OperatorIfc.OPERATOR_PRECEDENCE_COMPARISON;
19
+
20
+
import com.ezylang.evalex.Expression;
21
+
import com.ezylang.evalex.data.EvaluationValue;
22
+
import com.ezylang.evalex.operators.AbstractOperator;
23
+
import com.ezylang.evalex.operators.InfixOperator;
24
+
import com.ezylang.evalex.parser.Token;
25
+
26
+
/** Greater or equals of two values. */
27
+
@InfixOperator(precedence = OPERATOR_PRECEDENCE_COMPARISON)
28
+
public class InfixGreaterEqualsOperator extends AbstractOperator {
29
+
30
+
@Override
31
+
public EvaluationValue evaluate(
32
+
Expression expression, Token operatorToken, EvaluationValue... operands) {
33
+
return new EvaluationValue(operands[0].compareTo(operands[1]) >= 0);
34
+
}
35
+
}
+35
src/main/java/com/ezylang/evalex/operators/booleans/InfixGreaterOperator.java
+35
src/main/java/com/ezylang/evalex/operators/booleans/InfixGreaterOperator.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.booleans;
17
+
18
+
import static com.ezylang.evalex.operators.OperatorIfc.OPERATOR_PRECEDENCE_COMPARISON;
19
+
20
+
import com.ezylang.evalex.Expression;
21
+
import com.ezylang.evalex.data.EvaluationValue;
22
+
import com.ezylang.evalex.operators.AbstractOperator;
23
+
import com.ezylang.evalex.operators.InfixOperator;
24
+
import com.ezylang.evalex.parser.Token;
25
+
26
+
/** Greater of two values. */
27
+
@InfixOperator(precedence = OPERATOR_PRECEDENCE_COMPARISON)
28
+
public class InfixGreaterOperator extends AbstractOperator {
29
+
30
+
@Override
31
+
public EvaluationValue evaluate(
32
+
Expression expression, Token operatorToken, EvaluationValue... operands) {
33
+
return new EvaluationValue(operands[0].compareTo(operands[1]) > 0);
34
+
}
35
+
}
+35
src/main/java/com/ezylang/evalex/operators/booleans/InfixLessEqualsOperator.java
+35
src/main/java/com/ezylang/evalex/operators/booleans/InfixLessEqualsOperator.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.booleans;
17
+
18
+
import static com.ezylang.evalex.operators.OperatorIfc.OPERATOR_PRECEDENCE_COMPARISON;
19
+
20
+
import com.ezylang.evalex.Expression;
21
+
import com.ezylang.evalex.data.EvaluationValue;
22
+
import com.ezylang.evalex.operators.AbstractOperator;
23
+
import com.ezylang.evalex.operators.InfixOperator;
24
+
import com.ezylang.evalex.parser.Token;
25
+
26
+
/** Less or equals of two values. */
27
+
@InfixOperator(precedence = OPERATOR_PRECEDENCE_COMPARISON)
28
+
public class InfixLessEqualsOperator extends AbstractOperator {
29
+
30
+
@Override
31
+
public EvaluationValue evaluate(
32
+
Expression expression, Token operatorToken, EvaluationValue... operands) {
33
+
return new EvaluationValue(operands[0].compareTo(operands[1]) <= 0);
34
+
}
35
+
}
+35
src/main/java/com/ezylang/evalex/operators/booleans/InfixLessOperator.java
+35
src/main/java/com/ezylang/evalex/operators/booleans/InfixLessOperator.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.booleans;
17
+
18
+
import static com.ezylang.evalex.operators.OperatorIfc.OPERATOR_PRECEDENCE_COMPARISON;
19
+
20
+
import com.ezylang.evalex.Expression;
21
+
import com.ezylang.evalex.data.EvaluationValue;
22
+
import com.ezylang.evalex.operators.AbstractOperator;
23
+
import com.ezylang.evalex.operators.InfixOperator;
24
+
import com.ezylang.evalex.parser.Token;
25
+
26
+
/** Less of two values. */
27
+
@InfixOperator(precedence = OPERATOR_PRECEDENCE_COMPARISON)
28
+
public class InfixLessOperator extends AbstractOperator {
29
+
30
+
@Override
31
+
public EvaluationValue evaluate(
32
+
Expression expression, Token operatorToken, EvaluationValue... operands) {
33
+
return new EvaluationValue(operands[0].compareTo(operands[1]) < 0);
34
+
}
35
+
}
+35
src/main/java/com/ezylang/evalex/operators/booleans/InfixNotEqualsOperator.java
+35
src/main/java/com/ezylang/evalex/operators/booleans/InfixNotEqualsOperator.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.booleans;
17
+
18
+
import static com.ezylang.evalex.operators.OperatorIfc.OPERATOR_PRECEDENCE_EQUALITY;
19
+
20
+
import com.ezylang.evalex.Expression;
21
+
import com.ezylang.evalex.data.EvaluationValue;
22
+
import com.ezylang.evalex.operators.AbstractOperator;
23
+
import com.ezylang.evalex.operators.InfixOperator;
24
+
import com.ezylang.evalex.parser.Token;
25
+
26
+
/** No equality of two values. */
27
+
@InfixOperator(precedence = OPERATOR_PRECEDENCE_EQUALITY)
28
+
public class InfixNotEqualsOperator extends AbstractOperator {
29
+
30
+
@Override
31
+
public EvaluationValue evaluate(
32
+
Expression expression, Token operatorToken, EvaluationValue... operands) {
33
+
return new EvaluationValue(!operands[0].equals(operands[1]));
34
+
}
35
+
}
+35
src/main/java/com/ezylang/evalex/operators/booleans/InfixOrOperator.java
+35
src/main/java/com/ezylang/evalex/operators/booleans/InfixOrOperator.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.booleans;
17
+
18
+
import static com.ezylang.evalex.operators.OperatorIfc.OPERATOR_PRECEDENCE_OR;
19
+
20
+
import com.ezylang.evalex.Expression;
21
+
import com.ezylang.evalex.data.EvaluationValue;
22
+
import com.ezylang.evalex.operators.AbstractOperator;
23
+
import com.ezylang.evalex.operators.InfixOperator;
24
+
import com.ezylang.evalex.parser.Token;
25
+
26
+
/** Boolean OR of two values. */
27
+
@InfixOperator(precedence = OPERATOR_PRECEDENCE_OR)
28
+
public class InfixOrOperator extends AbstractOperator {
29
+
30
+
@Override
31
+
public EvaluationValue evaluate(
32
+
Expression expression, Token operatorToken, EvaluationValue... operands) {
33
+
return new EvaluationValue(operands[0].getBooleanValue() || operands[1].getBooleanValue());
34
+
}
35
+
}
+33
src/main/java/com/ezylang/evalex/operators/booleans/PrefixNotOperator.java
+33
src/main/java/com/ezylang/evalex/operators/booleans/PrefixNotOperator.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.booleans;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.operators.AbstractOperator;
21
+
import com.ezylang.evalex.operators.PrefixOperator;
22
+
import com.ezylang.evalex.parser.Token;
23
+
24
+
/** Boolean negation of value. */
25
+
@PrefixOperator
26
+
public class PrefixNotOperator extends AbstractOperator {
27
+
28
+
@Override
29
+
public EvaluationValue evaluate(
30
+
Expression expression, Token operatorToken, EvaluationValue... operands) {
31
+
return new EvaluationValue(!operands[0].getBooleanValue());
32
+
}
33
+
}
+68
src/main/java/com/ezylang/evalex/parser/ASTNode.java
+68
src/main/java/com/ezylang/evalex/parser/ASTNode.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import java.util.Arrays;
19
+
import java.util.List;
20
+
import java.util.stream.Collectors;
21
+
import lombok.Value;
22
+
23
+
/**
24
+
* Expressions are parsed into an abstract syntax tree (AST). The tree has one root node and each
25
+
* node has zero or more children (parameters), depending on the operation. A leaf node is a
26
+
* numerical or string constant that has no more children (parameters). Other nodes define
27
+
* operators, functions and special operations like array index and structure separation.
28
+
*
29
+
* <p>The tree is evaluated from bottom (leafs) to top, in a recursive way, until the root node is
30
+
* evaluated, which then holds the result of the complete expression.
31
+
*
32
+
* <p>To be able to visualize the tree, a <code>toJSON</code> method is provided. The produced JSON
33
+
* string can be used to visualize the tree. OE.g. with this online tool:
34
+
*
35
+
* <p><a href="https://vanya.jp.net/vtree/">Online JSON to Tree Diagram Converter</a>
36
+
*/
37
+
@Value
38
+
public class ASTNode {
39
+
40
+
/** The children od the tree. */
41
+
List<ASTNode> parameters;
42
+
43
+
/** The token associated with this tree node. */
44
+
Token token;
45
+
46
+
public ASTNode(Token token, ASTNode... parameters) {
47
+
this.token = token;
48
+
this.parameters = Arrays.asList(parameters);
49
+
}
50
+
51
+
/**
52
+
* Produces a JSON string representation of this node ad all its children.
53
+
*
54
+
* @return A JSON string of the tree structure starting at this node.
55
+
*/
56
+
public String toJSON() {
57
+
if (parameters.isEmpty()) {
58
+
return String.format(
59
+
"{" + "\"type\":\"%s\",\"value\":\"%s\"}", token.getType(), token.getValue());
60
+
} else {
61
+
String childrenJson =
62
+
parameters.stream().map(ASTNode::toJSON).collect(Collectors.joining(","));
63
+
return String.format(
64
+
"{" + "\"type\":\"%s\",\"value\":\"%s\",\"children\":[%s]}",
65
+
token.getType(), token.getValue(), childrenJson);
66
+
}
67
+
}
68
+
}
+40
src/main/java/com/ezylang/evalex/parser/ParseException.java
+40
src/main/java/com/ezylang/evalex/parser/ParseException.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import com.ezylang.evalex.BaseException;
19
+
import lombok.ToString;
20
+
21
+
/** Exception while parsing the expression. */
22
+
@ToString(callSuper = true)
23
+
public class ParseException extends BaseException {
24
+
25
+
public ParseException(int startPosition, int endPosition, String tokenString, String message) {
26
+
super(startPosition, endPosition, tokenString, message);
27
+
}
28
+
29
+
public ParseException(String expression, String message) {
30
+
super(1, expression.length(), expression, message);
31
+
}
32
+
33
+
public ParseException(Token token, String message) {
34
+
super(
35
+
token.getStartPosition(),
36
+
token.getStartPosition() + token.getValue().length() - 1,
37
+
token.getValue(),
38
+
message);
39
+
}
40
+
}
+287
src/main/java/com/ezylang/evalex/parser/ShuntingYardConverter.java
+287
src/main/java/com/ezylang/evalex/parser/ShuntingYardConverter.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import static com.ezylang.evalex.parser.Token.TokenType.ARRAY_INDEX;
19
+
import static com.ezylang.evalex.parser.Token.TokenType.ARRAY_OPEN;
20
+
import static com.ezylang.evalex.parser.Token.TokenType.BRACE_OPEN;
21
+
import static com.ezylang.evalex.parser.Token.TokenType.FUNCTION;
22
+
import static com.ezylang.evalex.parser.Token.TokenType.STRUCTURE_SEPARATOR;
23
+
24
+
import com.ezylang.evalex.config.ExpressionConfiguration;
25
+
import com.ezylang.evalex.functions.FunctionIfc;
26
+
import com.ezylang.evalex.operators.OperatorIfc;
27
+
import com.ezylang.evalex.parser.Token.TokenType;
28
+
import java.util.ArrayDeque;
29
+
import java.util.ArrayList;
30
+
import java.util.Deque;
31
+
import java.util.List;
32
+
33
+
/**
34
+
* The shunting yard algorithm can be used to convert a mathematical expression from an infix
35
+
* notation into either a postfix notation (RPN, reverse polish notation), or into an abstract
36
+
* syntax tree (AST).
37
+
*
38
+
* <p>Here it is used to parse and convert a list of already parsed expression tokens into an AST.
39
+
*
40
+
* @see <a href="https://en.wikipedia.org/wiki/Shunting_yard_algorithm">Shunting yard algorithm</a>
41
+
* @see <a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree">Abstract syntax tree</a>
42
+
*/
43
+
public class ShuntingYardConverter {
44
+
45
+
private final List<Token> expressionTokens;
46
+
47
+
private final String originalExpression;
48
+
49
+
private final ExpressionConfiguration configuration;
50
+
51
+
private final Deque<Token> operatorStack = new ArrayDeque<>();
52
+
private final Deque<ASTNode> operandStack = new ArrayDeque<>();
53
+
54
+
public ShuntingYardConverter(
55
+
String originalExpression,
56
+
List<Token> expressionTokens,
57
+
ExpressionConfiguration configuration) {
58
+
this.originalExpression = originalExpression;
59
+
this.expressionTokens = expressionTokens;
60
+
this.configuration = configuration;
61
+
}
62
+
63
+
public ASTNode toAbstractSyntaxTree() throws ParseException {
64
+
65
+
Token previousToken = null;
66
+
for (Token currentToken : expressionTokens) {
67
+
switch (currentToken.getType()) {
68
+
case VARIABLE_OR_CONSTANT:
69
+
case NUMBER_LITERAL:
70
+
case STRING_LITERAL:
71
+
operandStack.push(new ASTNode(currentToken));
72
+
break;
73
+
case FUNCTION:
74
+
operatorStack.push(currentToken);
75
+
break;
76
+
case COMMA:
77
+
processOperatorsFromStackUntilTokenType(BRACE_OPEN);
78
+
break;
79
+
case INFIX_OPERATOR:
80
+
case PREFIX_OPERATOR:
81
+
case POSTFIX_OPERATOR:
82
+
processOperator(currentToken);
83
+
break;
84
+
case BRACE_OPEN:
85
+
processBraceOpen(previousToken, currentToken);
86
+
break;
87
+
case BRACE_CLOSE:
88
+
processBraceClose();
89
+
break;
90
+
case ARRAY_OPEN:
91
+
processArrayOpen(currentToken);
92
+
break;
93
+
case ARRAY_CLOSE:
94
+
processArrayClose();
95
+
break;
96
+
case STRUCTURE_SEPARATOR:
97
+
processStructureSeparator(currentToken);
98
+
break;
99
+
default:
100
+
throw new ParseException(
101
+
currentToken, "Unexpected token of type '" + currentToken.getType() + "'");
102
+
}
103
+
previousToken = currentToken;
104
+
}
105
+
106
+
while (!operatorStack.isEmpty()) {
107
+
Token token = operatorStack.pop();
108
+
createOperatorNode(token);
109
+
}
110
+
111
+
if (operandStack.isEmpty()) {
112
+
throw new ParseException(this.originalExpression, "Empty expression");
113
+
}
114
+
115
+
return operandStack.pop();
116
+
}
117
+
118
+
private void processStructureSeparator(Token currentToken) throws ParseException {
119
+
Token nextToken = operatorStack.isEmpty() ? null : operatorStack.peek();
120
+
while (nextToken != null && nextToken.getType() == STRUCTURE_SEPARATOR) {
121
+
Token token = operatorStack.pop();
122
+
createOperatorNode(token);
123
+
nextToken = operatorStack.isEmpty() ? null : operatorStack.peek();
124
+
}
125
+
operatorStack.push(currentToken);
126
+
}
127
+
128
+
private void processBraceOpen(Token previousToken, Token currentToken) {
129
+
if (previousToken != null && previousToken.getType() == FUNCTION) {
130
+
// start of parameter list, marker for variable number of arguments
131
+
Token paramStart =
132
+
new Token(
133
+
currentToken.getStartPosition(),
134
+
currentToken.getValue(),
135
+
TokenType.FUNCTION_PARAM_START);
136
+
operandStack.push(new ASTNode(paramStart));
137
+
}
138
+
operatorStack.push(currentToken);
139
+
}
140
+
141
+
private void processBraceClose() throws ParseException {
142
+
processOperatorsFromStackUntilTokenType(BRACE_OPEN);
143
+
operatorStack.pop(); // throw away the marker
144
+
if (!operatorStack.isEmpty() && operatorStack.peek().getType() == FUNCTION) {
145
+
Token functionToken = operatorStack.pop();
146
+
ArrayList<ASTNode> parameters = new ArrayList<>();
147
+
while (!operandStack.isEmpty()) {
148
+
// add all parameters in reverse order from stack to the parameter array
149
+
ASTNode node = operandStack.pop();
150
+
if (node.getToken().getType() == TokenType.FUNCTION_PARAM_START) {
151
+
break;
152
+
}
153
+
parameters.add(0, node);
154
+
}
155
+
validateFunctionParameters(functionToken, parameters);
156
+
operandStack.push(new ASTNode(functionToken, parameters.toArray(new ASTNode[0])));
157
+
}
158
+
}
159
+
160
+
private void validateFunctionParameters(Token functionToken, ArrayList<ASTNode> parameters)
161
+
throws ParseException {
162
+
FunctionIfc function = functionToken.getFunctionDefinition();
163
+
if (parameters.size() < function.getFunctionParameterDefinitions().size()) {
164
+
throw new ParseException(functionToken, "Not enough parameters for function");
165
+
}
166
+
if (!function.hasVarArgs()
167
+
&& parameters.size() > function.getFunctionParameterDefinitions().size()) {
168
+
throw new ParseException(functionToken, "Too many parameters for function");
169
+
}
170
+
}
171
+
172
+
/**
173
+
* Array index is treated like a function with two parameters. First parameter is the array (name
174
+
* or evaluation result). Second parameter is the array index.
175
+
*
176
+
* @param currentToken The current ARRAY_OPEN ("[") token.
177
+
*/
178
+
private void processArrayOpen(Token currentToken) throws ParseException {
179
+
Token nextToken = operatorStack.isEmpty() ? null : operatorStack.peek();
180
+
while (nextToken != null && (nextToken.getType() == STRUCTURE_SEPARATOR)) {
181
+
Token token = operatorStack.pop();
182
+
createOperatorNode(token);
183
+
nextToken = operatorStack.isEmpty() ? null : operatorStack.peek();
184
+
}
185
+
// create ARRAY_INDEX operator (just like a function name) and push it to the operator stack
186
+
Token arrayIndex =
187
+
new Token(currentToken.getStartPosition(), currentToken.getValue(), ARRAY_INDEX);
188
+
operatorStack.push(arrayIndex);
189
+
190
+
// push the ARRAY_OPEN to the operators, too (to later match the ARRAY_CLOSE)
191
+
operatorStack.push(currentToken);
192
+
}
193
+
194
+
/**
195
+
* Follows the logic for a function, but with two fixed parameters.
196
+
*
197
+
* @throws ParseException If there were problems while processing the stacks.
198
+
*/
199
+
private void processArrayClose() throws ParseException {
200
+
processOperatorsFromStackUntilTokenType(ARRAY_OPEN);
201
+
operatorStack.pop(); // throw away the marker
202
+
Token arrayToken = operatorStack.pop();
203
+
ArrayList<ASTNode> operands = new ArrayList<>();
204
+
205
+
// second parameter of the "ARRAY_INDEX" function is the index (first on stack)
206
+
ASTNode index = operandStack.pop();
207
+
operands.add(0, index);
208
+
209
+
// first parameter of the "ARRAY_INDEX" function is the array (name or evaluation result)
210
+
// (second on stack)
211
+
ASTNode array = operandStack.pop();
212
+
operands.add(0, array);
213
+
214
+
operandStack.push(new ASTNode(arrayToken, operands.toArray(new ASTNode[0])));
215
+
}
216
+
217
+
private void processOperatorsFromStackUntilTokenType(TokenType untilTokenType)
218
+
throws ParseException {
219
+
while (!operatorStack.isEmpty() && operatorStack.peek().getType() != untilTokenType) {
220
+
Token token = operatorStack.pop();
221
+
createOperatorNode(token);
222
+
}
223
+
}
224
+
225
+
private void createOperatorNode(Token token) throws ParseException {
226
+
if (operandStack.isEmpty()) {
227
+
throw new ParseException(token, "Missing operand for operator");
228
+
}
229
+
230
+
ASTNode operand1 = operandStack.pop();
231
+
232
+
if (token.getType() == TokenType.PREFIX_OPERATOR
233
+
|| token.getType() == TokenType.POSTFIX_OPERATOR) {
234
+
operandStack.push(new ASTNode(token, operand1));
235
+
} else {
236
+
if (operandStack.isEmpty()) {
237
+
throw new ParseException(token, "Missing second operand for operator");
238
+
}
239
+
ASTNode operand2 = operandStack.pop();
240
+
operandStack.push(new ASTNode(token, operand2, operand1));
241
+
}
242
+
}
243
+
244
+
private void processOperator(Token currentToken) throws ParseException {
245
+
Token nextToken = operatorStack.isEmpty() ? null : operatorStack.peek();
246
+
while (isOperator(nextToken)
247
+
&& isNextOperatorOfHigherPrecedence(
248
+
currentToken.getOperatorDefinition(), nextToken.getOperatorDefinition())) {
249
+
Token token = operatorStack.pop();
250
+
createOperatorNode(token);
251
+
nextToken = operatorStack.isEmpty() ? null : operatorStack.peek();
252
+
}
253
+
operatorStack.push(currentToken);
254
+
}
255
+
256
+
private boolean isNextOperatorOfHigherPrecedence(
257
+
OperatorIfc currentOperator, OperatorIfc nextOperator) {
258
+
// structure operator (null) has always a higher precedence than other operators
259
+
if (nextOperator == null) {
260
+
return true;
261
+
}
262
+
263
+
if (currentOperator.isLeftAssociative()) {
264
+
return currentOperator.getPrecedence(configuration)
265
+
<= nextOperator.getPrecedence(configuration);
266
+
} else {
267
+
return currentOperator.getPrecedence(configuration)
268
+
< nextOperator.getPrecedence(configuration);
269
+
}
270
+
}
271
+
272
+
private boolean isOperator(Token token) {
273
+
if (token == null) {
274
+
return false;
275
+
}
276
+
TokenType tokenType = token.getType();
277
+
switch (tokenType) {
278
+
case INFIX_OPERATOR:
279
+
case PREFIX_OPERATOR:
280
+
case POSTFIX_OPERATOR:
281
+
case STRUCTURE_SEPARATOR:
282
+
return true;
283
+
default:
284
+
return false;
285
+
}
286
+
}
287
+
}
+76
src/main/java/com/ezylang/evalex/parser/Token.java
+76
src/main/java/com/ezylang/evalex/parser/Token.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import com.ezylang.evalex.functions.FunctionIfc;
19
+
import com.ezylang.evalex.operators.OperatorIfc;
20
+
import lombok.AllArgsConstructor;
21
+
import lombok.EqualsAndHashCode;
22
+
import lombok.ToString;
23
+
import lombok.Value;
24
+
25
+
/**
26
+
* A token represents a singe part of an expression, like an operator, number literal, or a brace.
27
+
* Each token has a unique type, a value (its representation) and a position (starting with 1) in
28
+
* the original expression string.
29
+
*
30
+
* <p>For operators and functions, the operator and function definition is also set during parsing.
31
+
*/
32
+
@Value
33
+
@AllArgsConstructor
34
+
@EqualsAndHashCode(doNotUseGetters = true)
35
+
public class Token {
36
+
37
+
public enum TokenType {
38
+
BRACE_OPEN,
39
+
BRACE_CLOSE,
40
+
COMMA,
41
+
STRING_LITERAL,
42
+
NUMBER_LITERAL,
43
+
VARIABLE_OR_CONSTANT,
44
+
INFIX_OPERATOR,
45
+
PREFIX_OPERATOR,
46
+
POSTFIX_OPERATOR,
47
+
FUNCTION,
48
+
FUNCTION_PARAM_START,
49
+
ARRAY_OPEN,
50
+
ARRAY_CLOSE,
51
+
ARRAY_INDEX,
52
+
STRUCTURE_SEPARATOR
53
+
}
54
+
55
+
int startPosition;
56
+
57
+
String value;
58
+
59
+
TokenType type;
60
+
61
+
@EqualsAndHashCode.Exclude @ToString.Exclude FunctionIfc functionDefinition;
62
+
63
+
@EqualsAndHashCode.Exclude @ToString.Exclude OperatorIfc operatorDefinition;
64
+
65
+
public Token(int startPosition, String value, TokenType type) {
66
+
this(startPosition, value, type, null, null);
67
+
}
68
+
69
+
public Token(int startPosition, String value, TokenType type, FunctionIfc functionDefinition) {
70
+
this(startPosition, value, type, functionDefinition, null);
71
+
}
72
+
73
+
public Token(int startPosition, String value, TokenType type, OperatorIfc operatorDefinition) {
74
+
this(startPosition, value, type, null, operatorDefinition);
75
+
}
76
+
}
+507
src/main/java/com/ezylang/evalex/parser/Tokenizer.java
+507
src/main/java/com/ezylang/evalex/parser/Tokenizer.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import static com.ezylang.evalex.parser.Token.TokenType.BRACE_OPEN;
19
+
20
+
import com.ezylang.evalex.config.ExpressionConfiguration;
21
+
import com.ezylang.evalex.config.FunctionDictionaryIfc;
22
+
import com.ezylang.evalex.config.OperatorDictionaryIfc;
23
+
import com.ezylang.evalex.functions.FunctionIfc;
24
+
import com.ezylang.evalex.operators.OperatorIfc;
25
+
import com.ezylang.evalex.parser.Token.TokenType;
26
+
import java.util.ArrayList;
27
+
import java.util.List;
28
+
29
+
/**
30
+
* The tokenizer is responsible to parse a string and return a list of tokens. The order of tokens
31
+
* will follow the infix expression notation, skipping any blank characters.
32
+
*/
33
+
public class Tokenizer {
34
+
35
+
private final String expressionString;
36
+
37
+
private final OperatorDictionaryIfc operatorDictionary;
38
+
39
+
private final FunctionDictionaryIfc functionDictionary;
40
+
41
+
private final ExpressionConfiguration configuration;
42
+
43
+
private final List<Token> tokens = new ArrayList<>();
44
+
45
+
private int currentColumnIndex = 0;
46
+
47
+
private int currentChar = -2;
48
+
49
+
private int braceBalance;
50
+
51
+
private int arrayBalance;
52
+
53
+
public Tokenizer(String expressionString, ExpressionConfiguration configuration) {
54
+
this.expressionString = expressionString;
55
+
this.configuration = configuration;
56
+
this.operatorDictionary = configuration.getOperatorDictionary();
57
+
this.functionDictionary = configuration.getFunctionDictionary();
58
+
}
59
+
60
+
/**
61
+
* Parse the given expression and return a list of tokens, representing the expression.
62
+
*
63
+
* @return A list of expression tokens.
64
+
* @throws ParseException When the expression can't be parsed.
65
+
*/
66
+
public List<Token> parse() throws ParseException {
67
+
Token currentToken = getNextToken();
68
+
while (currentToken != null) {
69
+
if (currentToken.getType() == BRACE_OPEN && implicitMultiplicationPossible()) {
70
+
if (configuration.isImplicitMultiplicationAllowed()) {
71
+
Token multiplication =
72
+
new Token(currentToken.getStartPosition(), "*", TokenType.INFIX_OPERATOR);
73
+
tokens.add(multiplication);
74
+
} else {
75
+
throw new ParseException(currentToken, "Missing operator");
76
+
}
77
+
}
78
+
tokens.add(currentToken);
79
+
currentToken = getNextToken();
80
+
}
81
+
82
+
if (braceBalance > 0) {
83
+
throw new ParseException(expressionString, "Closing brace not found");
84
+
}
85
+
86
+
if (arrayBalance > 0) {
87
+
throw new ParseException(expressionString, "Closing array not found");
88
+
}
89
+
90
+
return tokens;
91
+
}
92
+
93
+
private Token getNextToken() throws ParseException {
94
+
95
+
// blanks are always skipped.
96
+
skipBlanks();
97
+
98
+
// end of input
99
+
if (currentChar == -1) {
100
+
return null;
101
+
}
102
+
103
+
// we have a token start, identify and parse it
104
+
if (currentChar == '"') {
105
+
return parseStringLiteral();
106
+
} else if (currentChar == '(') {
107
+
return parseBraceOpen();
108
+
} else if (currentChar == ')') {
109
+
return parseBraceClose();
110
+
} else if (currentChar == '[' && configuration.isArraysAllowed()) {
111
+
return parseArrayOpen();
112
+
} else if (currentChar == ']' && configuration.isArraysAllowed()) {
113
+
return parseArrayClose();
114
+
} else if (currentChar == '.'
115
+
&& !isNumberChar(peekNextChar())
116
+
&& configuration.isStructuresAllowed()) {
117
+
return parseStructureSeparator();
118
+
} else if (currentChar == ',') {
119
+
Token token = new Token(currentColumnIndex, ",", TokenType.COMMA);
120
+
consumeChar();
121
+
return token;
122
+
} else if (isIdentifierStart(currentChar)) {
123
+
return parseIdentifier();
124
+
} else if (isNumberStart(currentChar)) {
125
+
return parseNumberLiteral();
126
+
} else {
127
+
return parseOperator();
128
+
}
129
+
}
130
+
131
+
private Token parseStructureSeparator() throws ParseException {
132
+
Token token = new Token(currentColumnIndex, ".", TokenType.STRUCTURE_SEPARATOR);
133
+
if (arrayOpenOrStructureSeparatorNotAllowed()) {
134
+
throw new ParseException(token, "Structure separator not allowed here");
135
+
}
136
+
consumeChar();
137
+
return token;
138
+
}
139
+
140
+
private Token parseArrayClose() throws ParseException {
141
+
Token token = new Token(currentColumnIndex, "]", TokenType.ARRAY_CLOSE);
142
+
if (!arrayCloseAllowed()) {
143
+
throw new ParseException(token, "Array close not allowed here");
144
+
}
145
+
consumeChar();
146
+
arrayBalance--;
147
+
if (arrayBalance < 0) {
148
+
throw new ParseException(token, "Unexpected closing array");
149
+
}
150
+
return token;
151
+
}
152
+
153
+
private Token parseArrayOpen() throws ParseException {
154
+
Token token = new Token(currentColumnIndex, "[", TokenType.ARRAY_OPEN);
155
+
if (arrayOpenOrStructureSeparatorNotAllowed()) {
156
+
throw new ParseException(token, "Array open not allowed here");
157
+
}
158
+
consumeChar();
159
+
arrayBalance++;
160
+
return token;
161
+
}
162
+
163
+
private Token parseBraceClose() throws ParseException {
164
+
Token token = new Token(currentColumnIndex, ")", TokenType.BRACE_CLOSE);
165
+
consumeChar();
166
+
braceBalance--;
167
+
if (braceBalance < 0) {
168
+
throw new ParseException(token, "Unexpected closing brace");
169
+
}
170
+
return token;
171
+
}
172
+
173
+
private Token parseBraceOpen() {
174
+
Token token = new Token(currentColumnIndex, "(", BRACE_OPEN);
175
+
consumeChar();
176
+
braceBalance++;
177
+
return token;
178
+
}
179
+
180
+
private Token getPreviousToken() {
181
+
return tokens.isEmpty() ? null : tokens.get(tokens.size() - 1);
182
+
}
183
+
184
+
private Token parseOperator() throws ParseException {
185
+
int tokenStartIndex = currentColumnIndex;
186
+
StringBuilder tokenValue = new StringBuilder();
187
+
while (currentChar != -1 && isNotOtherTokenStart(currentChar)) {
188
+
tokenValue.append((char) currentChar);
189
+
String tokenString = tokenValue.toString();
190
+
String possibleNextOperator = tokenString + (char) peekNextChar();
191
+
boolean possibleNextOperatorFound =
192
+
(prefixOperatorAllowed() && operatorDictionary.hasPrefixOperator(possibleNextOperator))
193
+
|| (postfixOperatorAllowed()
194
+
&& operatorDictionary.hasPostfixOperator(possibleNextOperator))
195
+
|| (infixOperatorAllowed()
196
+
&& operatorDictionary.hasInfixOperator(possibleNextOperator));
197
+
consumeChar();
198
+
if (!possibleNextOperatorFound) {
199
+
break;
200
+
}
201
+
}
202
+
String tokenString = tokenValue.toString();
203
+
if (prefixOperatorAllowed() && operatorDictionary.hasPrefixOperator(tokenString)) {
204
+
OperatorIfc operator = operatorDictionary.getPrefixOperator(tokenString);
205
+
return new Token(tokenStartIndex, tokenString, TokenType.PREFIX_OPERATOR, operator);
206
+
} else if (postfixOperatorAllowed() && operatorDictionary.hasPostfixOperator(tokenString)) {
207
+
OperatorIfc operator = operatorDictionary.getPostfixOperator(tokenString);
208
+
return new Token(tokenStartIndex, tokenString, TokenType.POSTFIX_OPERATOR, operator);
209
+
} else if (operatorDictionary.hasInfixOperator(tokenString)) {
210
+
OperatorIfc operator = operatorDictionary.getInfixOperator(tokenString);
211
+
return new Token(tokenStartIndex, tokenString, TokenType.INFIX_OPERATOR, operator);
212
+
}
213
+
throw new ParseException(
214
+
tokenStartIndex,
215
+
tokenStartIndex + tokenString.length() - 1,
216
+
tokenString,
217
+
"Undefined operator '" + tokenString + "'");
218
+
}
219
+
220
+
private boolean implicitMultiplicationPossible() {
221
+
Token previousToken = getPreviousToken();
222
+
223
+
if (previousToken == null) {
224
+
return false;
225
+
}
226
+
227
+
switch (previousToken.getType()) {
228
+
case BRACE_CLOSE:
229
+
case NUMBER_LITERAL:
230
+
return true;
231
+
default:
232
+
return false;
233
+
}
234
+
}
235
+
236
+
private boolean arrayOpenOrStructureSeparatorNotAllowed() {
237
+
Token previousToken = getPreviousToken();
238
+
239
+
if (previousToken == null) {
240
+
return true;
241
+
}
242
+
243
+
switch (previousToken.getType()) {
244
+
case BRACE_CLOSE:
245
+
case VARIABLE_OR_CONSTANT:
246
+
case ARRAY_CLOSE:
247
+
return false;
248
+
default:
249
+
return true;
250
+
}
251
+
}
252
+
253
+
private boolean arrayCloseAllowed() {
254
+
Token previousToken = getPreviousToken();
255
+
256
+
if (previousToken == null) {
257
+
return false;
258
+
}
259
+
260
+
switch (previousToken.getType()) {
261
+
case BRACE_OPEN:
262
+
case INFIX_OPERATOR:
263
+
case PREFIX_OPERATOR:
264
+
case FUNCTION:
265
+
case COMMA:
266
+
case ARRAY_OPEN:
267
+
return false;
268
+
default:
269
+
return true;
270
+
}
271
+
}
272
+
273
+
private boolean prefixOperatorAllowed() {
274
+
Token previousToken = getPreviousToken();
275
+
276
+
if (previousToken == null) {
277
+
return true;
278
+
}
279
+
280
+
switch (previousToken.getType()) {
281
+
case BRACE_OPEN:
282
+
case INFIX_OPERATOR:
283
+
case COMMA:
284
+
case PREFIX_OPERATOR:
285
+
return true;
286
+
default:
287
+
return false;
288
+
}
289
+
}
290
+
291
+
private boolean postfixOperatorAllowed() {
292
+
Token previousToken = getPreviousToken();
293
+
294
+
if (previousToken == null) {
295
+
return false;
296
+
}
297
+
298
+
switch (previousToken.getType()) {
299
+
case BRACE_CLOSE:
300
+
case NUMBER_LITERAL:
301
+
case VARIABLE_OR_CONSTANT:
302
+
case STRING_LITERAL:
303
+
return true;
304
+
default:
305
+
return false;
306
+
}
307
+
}
308
+
309
+
private boolean infixOperatorAllowed() {
310
+
Token previousToken = getPreviousToken();
311
+
312
+
if (previousToken == null) {
313
+
return false;
314
+
}
315
+
316
+
switch (previousToken.getType()) {
317
+
case BRACE_CLOSE:
318
+
case VARIABLE_OR_CONSTANT:
319
+
case STRING_LITERAL:
320
+
case POSTFIX_OPERATOR:
321
+
case NUMBER_LITERAL:
322
+
return true;
323
+
default:
324
+
return false;
325
+
}
326
+
}
327
+
328
+
private Token parseNumberLiteral() {
329
+
int tokenStartIndex = currentColumnIndex;
330
+
StringBuilder tokenValue = new StringBuilder();
331
+
int nextChar = peekNextChar();
332
+
if (currentChar == '0' && (nextChar == 'x' || nextChar == 'X')) {
333
+
// hexadecimal number, consume "0x"
334
+
tokenValue.append((char) currentChar);
335
+
consumeChar();
336
+
tokenValue.append((char) currentChar);
337
+
consumeChar();
338
+
while (currentChar != -1 && isHexChar(currentChar)) {
339
+
tokenValue.append((char) currentChar);
340
+
consumeChar();
341
+
}
342
+
} else {
343
+
// decimal number
344
+
while (currentChar != -1 && isNumberChar(currentChar)) {
345
+
tokenValue.append((char) currentChar);
346
+
consumeChar();
347
+
}
348
+
}
349
+
return new Token(tokenStartIndex, tokenValue.toString(), TokenType.NUMBER_LITERAL);
350
+
}
351
+
352
+
private Token parseIdentifier() throws ParseException {
353
+
int tokenStartIndex = currentColumnIndex;
354
+
StringBuilder tokenValue = new StringBuilder();
355
+
while (currentChar != -1 && isIdentifierChar(currentChar)) {
356
+
tokenValue.append((char) currentChar);
357
+
consumeChar();
358
+
}
359
+
String tokenName = tokenValue.toString();
360
+
361
+
if (prefixOperatorAllowed() && operatorDictionary.hasPrefixOperator(tokenName)) {
362
+
return new Token(
363
+
tokenStartIndex,
364
+
tokenName,
365
+
TokenType.PREFIX_OPERATOR,
366
+
operatorDictionary.getPrefixOperator(tokenName));
367
+
} else if (postfixOperatorAllowed() && operatorDictionary.hasPostfixOperator(tokenName)) {
368
+
return new Token(
369
+
tokenStartIndex,
370
+
tokenName,
371
+
TokenType.POSTFIX_OPERATOR,
372
+
operatorDictionary.getPostfixOperator(tokenName));
373
+
} else if (infixOperatorAllowed() && operatorDictionary.hasInfixOperator(tokenName)) {
374
+
return new Token(
375
+
tokenStartIndex,
376
+
tokenName,
377
+
TokenType.INFIX_OPERATOR,
378
+
operatorDictionary.getInfixOperator(tokenName));
379
+
}
380
+
381
+
skipBlanks();
382
+
if (currentChar == '(') {
383
+
if (!functionDictionary.hasFunction(tokenName)) {
384
+
throw new ParseException(
385
+
tokenStartIndex,
386
+
currentColumnIndex,
387
+
tokenName,
388
+
"Undefined function '" + tokenName + "'");
389
+
}
390
+
FunctionIfc function = functionDictionary.getFunction(tokenName);
391
+
return new Token(tokenStartIndex, tokenName, TokenType.FUNCTION, function);
392
+
} else {
393
+
return new Token(tokenStartIndex, tokenName, TokenType.VARIABLE_OR_CONSTANT);
394
+
}
395
+
}
396
+
397
+
Token parseStringLiteral() throws ParseException {
398
+
int tokenStartIndex = currentColumnIndex;
399
+
StringBuilder tokenValue = new StringBuilder();
400
+
// skip starting quote
401
+
consumeChar();
402
+
while (currentChar != -1 && currentChar != '"' && peekPreviousChar() != '\\') {
403
+
tokenValue.append((char) currentChar);
404
+
consumeChar();
405
+
}
406
+
// skip trailing quote
407
+
if (currentChar == '"') {
408
+
consumeChar();
409
+
} else {
410
+
throw new ParseException(
411
+
tokenStartIndex, currentColumnIndex, tokenValue.toString(), "Closing quote not found");
412
+
}
413
+
return new Token(tokenStartIndex, tokenValue.toString(), TokenType.STRING_LITERAL);
414
+
}
415
+
416
+
private boolean isNotOtherTokenStart(int ch) {
417
+
return !(Character.isWhitespace(ch)
418
+
|| isNumberStart(ch)
419
+
|| isIdentifierStart(ch)
420
+
|| ch == '"'
421
+
|| ch == '('
422
+
|| ch == ')'
423
+
|| ch == ',');
424
+
}
425
+
426
+
private boolean isNumberStart(int ch) {
427
+
if (Character.isDigit(ch)) {
428
+
return true;
429
+
}
430
+
return ch == '.' && Character.isDigit(peekNextChar());
431
+
}
432
+
433
+
private boolean isNumberChar(int ch) {
434
+
int previousChar = peekPreviousChar();
435
+
if (previousChar == 'e' || previousChar == 'E') {
436
+
return Character.isDigit(ch) || ch == '.' || ch == '+' || ch == '-';
437
+
} else {
438
+
return Character.isDigit(ch) || ch == '.' || ch == 'e' || ch == 'E';
439
+
}
440
+
}
441
+
442
+
private boolean isHexChar(int ch) {
443
+
switch (ch) {
444
+
case '0':
445
+
case '1':
446
+
case '2':
447
+
case '3':
448
+
case '4':
449
+
case '5':
450
+
case '6':
451
+
case '7':
452
+
case '8':
453
+
case '9':
454
+
case 'a':
455
+
case 'b':
456
+
case 'c':
457
+
case 'd':
458
+
case 'e':
459
+
case 'f':
460
+
case 'A':
461
+
case 'B':
462
+
case 'C':
463
+
case 'D':
464
+
case 'E':
465
+
case 'F':
466
+
return true;
467
+
default:
468
+
return false;
469
+
}
470
+
}
471
+
472
+
private boolean isIdentifierStart(int ch) {
473
+
return Character.isLetter(ch) || ch == '_';
474
+
}
475
+
476
+
private boolean isIdentifierChar(int ch) {
477
+
return Character.isLetter(ch) || Character.isDigit(ch) || ch == '_';
478
+
}
479
+
480
+
private void skipBlanks() {
481
+
if (currentChar == -2) {
482
+
// consume first character of expression
483
+
consumeChar();
484
+
}
485
+
while (currentChar != -1 && Character.isWhitespace(currentChar)) {
486
+
consumeChar();
487
+
}
488
+
}
489
+
490
+
private int peekNextChar() {
491
+
return currentColumnIndex == expressionString.length()
492
+
? -1
493
+
: expressionString.charAt(currentColumnIndex);
494
+
}
495
+
496
+
private int peekPreviousChar() {
497
+
return currentColumnIndex == 1 ? -1 : expressionString.charAt(currentColumnIndex - 2);
498
+
}
499
+
500
+
private void consumeChar() {
501
+
if (currentColumnIndex == expressionString.length()) {
502
+
currentChar = -1;
503
+
} else {
504
+
currentChar = expressionString.charAt(currentColumnIndex++);
505
+
}
506
+
}
507
+
}
-88
src/main/java/com/udojava/evalex/AbstractFunction.java
-88
src/main/java/com/udojava/evalex/AbstractFunction.java
···
1
-
/*
2
-
* Copyright 2018 Udo Klimaschewski
3
-
*
4
-
* http://UdoJava.com/
5
-
* http://about.me/udo.klimaschewski
6
-
*
7
-
* Permission is hereby granted, free of charge, to any person obtaining
8
-
* a copy of this software and associated documentation files (the
9
-
* "Software"), to deal in the Software without restriction, including
10
-
* without limitation the rights to use, copy, modify, merge, publish,
11
-
* distribute, sublicense, and/or sell copies of the Software, and to
12
-
* permit persons to whom the Software is furnished to do so, subject to
13
-
* the following conditions:
14
-
*
15
-
* The above copyright notice and this permission notice shall be
16
-
* included in all copies or substantial portions of the Software.
17
-
*
18
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
-
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
-
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
-
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
-
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
-
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
-
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
-
*
26
-
*/
27
-
package com.udojava.evalex;
28
-
29
-
import com.udojava.evalex.Expression.LazyNumber;
30
-
import java.math.BigDecimal;
31
-
import java.util.ArrayList;
32
-
import java.util.List;
33
-
34
-
/**
35
-
* Abstract implementation of a direct function.<br>
36
-
* <br>
37
-
* This abstract implementation does implement lazyEval so that it returns the result of eval.
38
-
*/
39
-
public abstract class AbstractFunction extends AbstractLazyFunction implements Function {
40
-
41
-
/**
42
-
* Creates a new function with given name and parameter count.
43
-
*
44
-
* @param name The name of the function.
45
-
* @param numParams The number of parameters for this function.
46
-
* <code>-1</code> denotes a variable number of parameters.
47
-
*/
48
-
protected AbstractFunction(String name, int numParams) {
49
-
super(name, numParams);
50
-
}
51
-
52
-
/**
53
-
* Creates a new function with given name and parameter count.
54
-
*
55
-
* @param name The name of the function.
56
-
* @param numParams The number of parameters for this function.
57
-
* <code>-1</code> denotes a variable number of parameters.
58
-
* @param booleanFunction Whether this function is a boolean function.
59
-
*/
60
-
protected AbstractFunction(String name, int numParams, boolean booleanFunction) {
61
-
super(name, numParams, booleanFunction);
62
-
}
63
-
64
-
public LazyNumber lazyEval(final List<LazyNumber> lazyParams) {
65
-
return new LazyNumber() {
66
-
67
-
private List<BigDecimal> params;
68
-
69
-
public BigDecimal eval() {
70
-
return AbstractFunction.this.eval(getParams());
71
-
}
72
-
73
-
public String getString() {
74
-
return String.valueOf(AbstractFunction.this.eval(getParams()));
75
-
}
76
-
77
-
private List<BigDecimal> getParams() {
78
-
if (params == null) {
79
-
params = new ArrayList<BigDecimal>();
80
-
for (LazyNumber lazyParam : lazyParams) {
81
-
params.add(lazyParam.eval());
82
-
}
83
-
}
84
-
return params;
85
-
}
86
-
};
87
-
}
88
-
}
-92
src/main/java/com/udojava/evalex/AbstractLazyFunction.java
-92
src/main/java/com/udojava/evalex/AbstractLazyFunction.java
···
1
-
/*
2
-
* Copyright 2018 Udo Klimaschewski
3
-
*
4
-
* http://UdoJava.com/
5
-
* http://about.me/udo.klimaschewski
6
-
*
7
-
* Permission is hereby granted, free of charge, to any person obtaining
8
-
* a copy of this software and associated documentation files (the
9
-
* "Software"), to deal in the Software without restriction, including
10
-
* without limitation the rights to use, copy, modify, merge, publish,
11
-
* distribute, sublicense, and/or sell copies of the Software, and to
12
-
* permit persons to whom the Software is furnished to do so, subject to
13
-
* the following conditions:
14
-
*
15
-
* The above copyright notice and this permission notice shall be
16
-
* included in all copies or substantial portions of the Software.
17
-
*
18
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
-
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
-
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
-
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
-
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
-
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
-
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
-
*
26
-
*/
27
-
package com.udojava.evalex;
28
-
29
-
import java.util.Locale;
30
-
31
-
/**
32
-
* Abstract implementation of a lazy function which implements all necessary methods with the
33
-
* exception of the main logic.
34
-
*/
35
-
public abstract class AbstractLazyFunction implements LazyFunction {
36
-
37
-
/**
38
-
* Name of this function.
39
-
*/
40
-
protected String name;
41
-
/**
42
-
* Number of parameters expected for this function. <code>-1</code> denotes a variable number of
43
-
* parameters.
44
-
*/
45
-
protected int numParams;
46
-
47
-
/**
48
-
* Whether this function is a boolean function.
49
-
*/
50
-
protected boolean booleanFunction;
51
-
52
-
/**
53
-
* Creates a new function with given name and parameter count.
54
-
*
55
-
* @param name The name of the function.
56
-
* @param numParams The number of parameters for this function.
57
-
* <code>-1</code> denotes a variable number of parameters.
58
-
* @param booleanFunction Whether this function is a boolean function.
59
-
*/
60
-
protected AbstractLazyFunction(String name, int numParams, boolean booleanFunction) {
61
-
this.name = name.toUpperCase(Locale.ROOT);
62
-
this.numParams = numParams;
63
-
this.booleanFunction = booleanFunction;
64
-
}
65
-
66
-
/**
67
-
* Creates a new function with given name and parameter count.
68
-
*
69
-
* @param name The name of the function.
70
-
* @param numParams The number of parameters for this function.
71
-
* <code>-1</code> denotes a variable number of parameters.
72
-
*/
73
-
protected AbstractLazyFunction(String name, int numParams) {
74
-
this(name, numParams, false);
75
-
}
76
-
77
-
public String getName() {
78
-
return name;
79
-
}
80
-
81
-
public int getNumParams() {
82
-
return numParams;
83
-
}
84
-
85
-
public boolean numParamsVaries() {
86
-
return numParams < 0;
87
-
}
88
-
89
-
public boolean isBooleanFunction() {
90
-
return booleanFunction;
91
-
}
92
-
}
-158
src/main/java/com/udojava/evalex/AbstractLazyOperator.java
-158
src/main/java/com/udojava/evalex/AbstractLazyOperator.java
···
1
-
/*
2
-
* Copyright 2018 Udo Klimaschewski
3
-
*
4
-
* http://UdoJava.com/
5
-
* http://about.me/udo.klimaschewski
6
-
*
7
-
* Permission is hereby granted, free of charge, to any person obtaining
8
-
* a copy of this software and associated documentation files (the
9
-
* "Software"), to deal in the Software without restriction, including
10
-
* without limitation the rights to use, copy, modify, merge, publish,
11
-
* distribute, sublicense, and/or sell copies of the Software, and to
12
-
* permit persons to whom the Software is furnished to do so, subject to
13
-
* the following conditions:
14
-
*
15
-
* The above copyright notice and this permission notice shall be
16
-
* included in all copies or substantial portions of the Software.
17
-
*
18
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
-
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
-
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
-
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
-
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
-
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
-
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
-
*
26
-
*/
27
-
package com.udojava.evalex;
28
-
29
-
/**
30
-
* Abstract implementation of an operator.
31
-
*/
32
-
public abstract class AbstractLazyOperator implements LazyOperator {
33
-
34
-
35
-
/**
36
-
* This operators name (pattern).
37
-
*/
38
-
protected String oper;
39
-
40
-
/**
41
-
* Operators precedence.
42
-
*/
43
-
protected int precedence;
44
-
45
-
/**
46
-
* Operator is left associative.
47
-
*/
48
-
protected boolean leftAssoc;
49
-
50
-
/**
51
-
* Operator is boolean.
52
-
*/
53
-
protected boolean booleanOperator = false;
54
-
55
-
/**
56
-
* Operator is unary, i.e. 1 or 2 operands expected for this operator.
57
-
*/
58
-
protected boolean unaryOperator;
59
-
60
-
61
-
/**
62
-
* Creates a new unary operator. This constructor allows the use of postfix unary operators.
63
-
*
64
-
* @param oper The operator name (pattern).
65
-
* @param precedence The operators precedence.
66
-
* @param leftAssoc <code>true</code> if the operator is left associative,
67
-
* else <code>false</code>.
68
-
* @param booleanOperator Whether this operator is boolean.
69
-
* @param unaryOperator <code>true</code> if the expected number of operands is 1,
70
-
* <code>false</code> if the expected number of operands is 2.
71
-
*/
72
-
protected AbstractLazyOperator(String oper, int precedence, boolean leftAssoc,
73
-
boolean booleanOperator, boolean unaryOperator) {
74
-
this.oper = oper;
75
-
this.precedence = precedence;
76
-
this.leftAssoc = leftAssoc;
77
-
this.booleanOperator = booleanOperator;
78
-
this.unaryOperator = unaryOperator;
79
-
}
80
-
81
-
82
-
/**
83
-
* Creates a new boolean operator.
84
-
*
85
-
* @param oper The operator name (pattern).
86
-
* @param precedence The operators precedence.
87
-
* @param leftAssoc <code>true</code> if the operator is left associative,
88
-
* else <code>false</code>.
89
-
* @param booleanOperator Whether this operator is boolean.
90
-
*/
91
-
protected AbstractLazyOperator(String oper, int precedence, boolean leftAssoc,
92
-
boolean booleanOperator) {
93
-
this.oper = oper;
94
-
this.precedence = precedence;
95
-
this.leftAssoc = leftAssoc;
96
-
this.booleanOperator = booleanOperator;
97
-
this.unaryOperator = false;
98
-
}
99
-
100
-
101
-
/**
102
-
* Creates a new operator.
103
-
*
104
-
* @param oper The operator name (pattern).
105
-
* @param precedence The operators precedence.
106
-
* @param leftAssoc <code>true</code> if the operator is left associative,
107
-
* else <code>false</code>.
108
-
*/
109
-
protected AbstractLazyOperator(String oper, int precedence, boolean leftAssoc) {
110
-
this.oper = oper;
111
-
this.precedence = precedence;
112
-
this.leftAssoc = leftAssoc;
113
-
this.unaryOperator = false;
114
-
}
115
-
116
-
117
-
/**
118
-
* @return The String that is used to denote the operator in the expression.
119
-
*/
120
-
public String getOper() {
121
-
return oper;
122
-
}
123
-
124
-
125
-
/**
126
-
* @return the precedence value of this operator.
127
-
*/
128
-
public int getPrecedence() {
129
-
return precedence;
130
-
}
131
-
132
-
133
-
/**
134
-
* @return <code>true</code> if this operator is left associative.
135
-
*/
136
-
public boolean isLeftAssoc() {
137
-
return leftAssoc;
138
-
}
139
-
140
-
141
-
/**
142
-
* @return <code>true</code> if this operator evaluates to a boolean
143
-
* expression.
144
-
*/
145
-
public boolean isBooleanOperator() {
146
-
return booleanOperator;
147
-
}
148
-
149
-
150
-
/**
151
-
* @return <code>true</code> if the number of operands for this operator is 1,
152
-
* or <code>false</code> if the number of operands is 2.
153
-
*/
154
-
public boolean isUnaryOperator() {
155
-
return unaryOperator;
156
-
}
157
-
158
-
}
-115
src/main/java/com/udojava/evalex/AbstractOperator.java
-115
src/main/java/com/udojava/evalex/AbstractOperator.java
···
1
-
/*
2
-
* Copyright 2018 Udo Klimaschewski
3
-
*
4
-
* http://UdoJava.com/
5
-
* http://about.me/udo.klimaschewski
6
-
*
7
-
* Permission is hereby granted, free of charge, to any person obtaining
8
-
* a copy of this software and associated documentation files (the
9
-
* "Software"), to deal in the Software without restriction, including
10
-
* without limitation the rights to use, copy, modify, merge, publish,
11
-
* distribute, sublicense, and/or sell copies of the Software, and to
12
-
* permit persons to whom the Software is furnished to do so, subject to
13
-
* the following conditions:
14
-
*
15
-
* The above copyright notice and this permission notice shall be
16
-
* included in all copies or substantial portions of the Software.
17
-
*
18
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
-
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
-
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
-
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
-
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
-
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
-
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
-
*
26
-
*/
27
-
package com.udojava.evalex;
28
-
29
-
import com.udojava.evalex.Expression.LazyNumber;
30
-
import java.math.BigDecimal;
31
-
32
-
/**
33
-
* Abstract implementation of an operator.
34
-
*/
35
-
public abstract class AbstractOperator extends AbstractLazyOperator implements Operator {
36
-
37
-
38
-
/**
39
-
* Creates a new unary operator.
40
-
*
41
-
* @param oper The operator name (pattern).
42
-
* @param precedence The operators precedence.
43
-
* @param leftAssoc <code>true</code> if the operator is left associative,
44
-
* else <code>false</code>.
45
-
* @param booleanOperator Whether this operator is boolean.
46
-
* @param unaryOperator Whether the operator is unary (<code>true</code>) or not
47
-
* (<code>false</code>).
48
-
*/
49
-
protected AbstractOperator(String oper, int precedence, boolean leftAssoc,
50
-
boolean booleanOperator, boolean unaryOperator) {
51
-
super(oper, precedence, leftAssoc, booleanOperator, unaryOperator);
52
-
}
53
-
54
-
55
-
/**
56
-
* Creates a new boolean operator.
57
-
*
58
-
* @param oper The operator name (pattern).
59
-
* @param precedence The operators precedence.
60
-
* @param leftAssoc <code>true</code> if the operator is left associative,
61
-
* else <code>false</code>.
62
-
* @param booleanOperator Whether this operator is boolean.
63
-
*/
64
-
protected AbstractOperator(String oper, int precedence, boolean leftAssoc,
65
-
boolean booleanOperator) {
66
-
super(oper, precedence, leftAssoc, booleanOperator);
67
-
}
68
-
69
-
70
-
/**
71
-
* Creates a new operator.
72
-
*
73
-
* @param oper The operator name (pattern).
74
-
* @param precedence The operators precedence.
75
-
* @param leftAssoc <code>true</code> if the operator is left associative,
76
-
* else <code>false</code>.
77
-
*/
78
-
protected AbstractOperator(String oper, int precedence, boolean leftAssoc) {
79
-
super(oper, precedence, leftAssoc);
80
-
}
81
-
82
-
83
-
/**
84
-
* Implementation of this operator supporting either 1 or 2 operands.
85
-
*
86
-
* @param v1 The first operand expected for the operation.
87
-
* @param v2 The second operand expected for the operation. For postfix unary operators, v2=null
88
-
* condition was added.
89
-
* @return LazyNumber object. The result of the operation.
90
-
*/
91
-
public LazyNumber eval(final LazyNumber v1, final LazyNumber v2) {
92
-
if (v2 == null) { // Condition to accept postfix unary operators (e.g. factorial operator '!')
93
-
return new LazyNumber() {
94
-
public BigDecimal eval() {
95
-
return AbstractOperator.this.eval(v1.eval(), null);
96
-
}
97
-
98
-
public String getString() {
99
-
return String.valueOf(AbstractOperator.this.eval(v1.eval(), null));
100
-
}
101
-
};
102
-
} else {
103
-
return new LazyNumber() {
104
-
public BigDecimal eval() {
105
-
return AbstractOperator.this.eval(v1.eval(), v2.eval());
106
-
}
107
-
108
-
public String getString() {
109
-
return String.valueOf(AbstractOperator.this.eval(v1.eval(), v2.eval()));
110
-
}
111
-
};
112
-
}
113
-
}
114
-
115
-
}
-96
src/main/java/com/udojava/evalex/AbstractUnaryOperator.java
-96
src/main/java/com/udojava/evalex/AbstractUnaryOperator.java
···
1
-
/*
2
-
* Copyright 2018 Udo Klimaschewski
3
-
*
4
-
* http://UdoJava.com/
5
-
* http://about.me/udo.klimaschewski
6
-
*
7
-
* Permission is hereby granted, free of charge, to any person obtaining
8
-
* a copy of this software and associated documentation files (the
9
-
* "Software"), to deal in the Software without restriction, including
10
-
* without limitation the rights to use, copy, modify, merge, publish,
11
-
* distribute, sublicense, and/or sell copies of the Software, and to
12
-
* permit persons to whom the Software is furnished to do so, subject to
13
-
* the following conditions:
14
-
*
15
-
* The above copyright notice and this permission notice shall be
16
-
* included in all copies or substantial portions of the Software.
17
-
*
18
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
-
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
-
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
-
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
-
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
-
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
-
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
-
*
26
-
*/
27
-
package com.udojava.evalex;
28
-
29
-
import com.udojava.evalex.Expression.ExpressionException;
30
-
import com.udojava.evalex.Expression.LazyNumber;
31
-
import java.math.BigDecimal;
32
-
33
-
/**
34
-
* Abstract implementation of an unary operator.<br>
35
-
* <br>
36
-
* This abstract implementation implements eval so that it forwards its first parameter to
37
-
* evalUnary.
38
-
*/
39
-
public abstract class AbstractUnaryOperator extends AbstractOperator {
40
-
41
-
42
-
/**
43
-
* Creates a new operator.
44
-
*
45
-
* @param oper The operator name (pattern).
46
-
* @param precedence The operators precedence.
47
-
* @param leftAssoc <code>true</code> if the operator is left associative,
48
-
* else <code>false</code>.
49
-
*/
50
-
protected AbstractUnaryOperator(String oper, int precedence, boolean leftAssoc) {
51
-
super(oper, precedence, leftAssoc);
52
-
}
53
-
54
-
55
-
@Override
56
-
public LazyNumber eval(final LazyNumber v1, final LazyNumber v2) {
57
-
if (v2 != null) {
58
-
throw new ExpressionException("Did not expect a second parameter for unary operator");
59
-
}
60
-
return new LazyNumber() {
61
-
@Override
62
-
public String getString() {
63
-
return String.valueOf(AbstractUnaryOperator.this.evalUnary(v1.eval()));
64
-
}
65
-
66
-
@Override
67
-
public BigDecimal eval() {
68
-
return AbstractUnaryOperator.this.evalUnary(v1.eval());
69
-
}
70
-
};
71
-
}
72
-
73
-
74
-
/**
75
-
* Implementation of this operator calling unary operator evaluation method..
76
-
*
77
-
* @param v1 The first parameter.
78
-
* @param v2 The second parameter. Expected to be null
79
-
* @return The result of the operation.
80
-
*/
81
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
82
-
if (v2 != null) {
83
-
throw new ExpressionException("Did not expect a second parameter for unary operator");
84
-
}
85
-
return evalUnary(v1);
86
-
}
87
-
88
-
89
-
/**
90
-
* Implementation of this prefix unary operator.
91
-
*
92
-
* @param v1 The parameter.
93
-
* @return The result of the operation.
94
-
*/
95
-
public abstract BigDecimal evalUnary(BigDecimal v1);
96
-
}
-2211
src/main/java/com/udojava/evalex/Expression.java
-2211
src/main/java/com/udojava/evalex/Expression.java
···
1
-
/*
2
-
* Copyright 2012-2020 Udo Klimaschewski
3
-
*
4
-
* http://UdoJava.com/
5
-
* http://about.me/udo.klimaschewski
6
-
*
7
-
* Permission is hereby granted, free of charge, to any person obtaining
8
-
* a copy of this software and associated documentation files (the
9
-
* "Software"), to deal in the Software without restriction, including
10
-
* without limitation the rights to use, copy, modify, merge, publish,
11
-
* distribute, sublicense, and/or sell copies of the Software, and to
12
-
* permit persons to whom the Software is furnished to do so, subject to
13
-
* the following conditions:
14
-
*
15
-
* The above copyright notice and this permission notice shall be
16
-
* included in all copies or substantial portions of the Software.
17
-
*
18
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
-
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
-
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
-
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
-
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
-
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
-
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
-
*
26
-
*/
27
-
package com.udojava.evalex;
28
-
29
-
import java.math.BigDecimal;
30
-
import java.math.BigInteger;
31
-
import java.math.MathContext;
32
-
import java.math.RoundingMode;
33
-
import java.util.ArrayDeque;
34
-
import java.util.ArrayList;
35
-
import java.util.Collections;
36
-
import java.util.Deque;
37
-
import java.util.HashMap;
38
-
import java.util.Iterator;
39
-
import java.util.List;
40
-
import java.util.Locale;
41
-
import java.util.Map;
42
-
import java.util.Set;
43
-
import java.util.Stack;
44
-
import java.util.TreeMap;
45
-
46
-
47
-
/**
48
-
* <h1>EvalEx - Java Expression Evaluator</h1>
49
-
*
50
-
* <h2>Introduction</h2> EvalEx is a handy expression evaluator for Java, that
51
-
* allows to evaluate simple mathematical and boolean expressions. <br> For more information, see:
52
-
* <a href="https://github.com/uklimaschewski/EvalEx">EvalEx GitHub
53
-
* repository</a>
54
-
* <ul>
55
-
* <li>The software is licensed under the MIT Open Source license (see <a href=
56
-
* "https://raw.githubusercontent.com/uklimaschewski/EvalEx/master/LICENSE">LICENSE
57
-
* file</a>).</li>
58
-
* <li>The *power of* operator (^) implementation was copied from <a href=
59
-
* "http://stackoverflow.com/questions/3579779/how-to-do-a-fractional-power-on-bigdecimal-in-java">Stack
60
-
* Overflow</a>. Thanks to Gene Marin.</li>
61
-
* <li>The SQRT() function implementation was taken from the book <a href=
62
-
* "http://www.amazon.de/Java-Number-Cruncher-Programmers-Numerical/dp/0130460419">The
63
-
* Java Programmers Guide To numerical Computing</a> (Ronald Mak, 2002).</li>
64
-
* </ul>
65
-
* <p>
66
-
* Thanks to all who contributed to this project: <a href=
67
-
* "https://github.com/uklimaschewski/EvalEx/graphs/contributors">Contributors</a>
68
-
*
69
-
* @see <a href="https://github.com/uklimaschewski/EvalEx">GitHub repository</a>
70
-
*/
71
-
public class Expression {
72
-
73
-
/**
74
-
* Unary operators precedence: + and - as prefix
75
-
*/
76
-
public static final int OPERATOR_PRECEDENCE_UNARY = 60;
77
-
78
-
/**
79
-
* Equality operators precedence: =, ==, !=. <>
80
-
*/
81
-
public static final int OPERATOR_PRECEDENCE_EQUALITY = 7;
82
-
83
-
/**
84
-
* Comparative operators precedence: <,>,<=,>=
85
-
*/
86
-
public static final int OPERATOR_PRECEDENCE_COMPARISON = 10;
87
-
88
-
/**
89
-
* Or operator precedence: ||
90
-
*/
91
-
public static final int OPERATOR_PRECEDENCE_OR = 2;
92
-
93
-
/**
94
-
* And operator precedence: &&
95
-
*/
96
-
public static final int OPERATOR_PRECEDENCE_AND = 4;
97
-
98
-
/**
99
-
* Power operator precedence: ^
100
-
*/
101
-
public static final int OPERATOR_PRECEDENCE_POWER = 40;
102
-
103
-
/**
104
-
* An optional higher power operator precedence. {@link ExpressionSettings}
105
-
*/
106
-
public static final int OPERATOR_PRECEDENCE_POWER_HIGHER = 80;
107
-
108
-
/**
109
-
* Multiplicative operators precedence: *,/,%
110
-
*/
111
-
public static final int OPERATOR_PRECEDENCE_MULTIPLICATIVE = 30;
112
-
113
-
/**
114
-
* Additive operators precedence: + and -
115
-
*/
116
-
public static final int OPERATOR_PRECEDENCE_ADDITIVE = 20;
117
-
118
-
/**
119
-
* Definition of PI as a constant, can be used in expressions as variable.
120
-
*/
121
-
public static final BigDecimal PI = new BigDecimal(
122
-
"3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679");
123
-
124
-
/**
125
-
* Definition of e: "Euler's number" as a constant, can be used in expressions as variable.
126
-
*/
127
-
public static final BigDecimal e = new BigDecimal(
128
-
"2.71828182845904523536028747135266249775724709369995957496696762772407663");
129
-
130
-
/**
131
-
* Exception message for missing operators.
132
-
*/
133
-
public static final String MISSING_PARAMETERS_FOR_OPERATOR = "Missing parameter(s) for operator ";
134
-
135
-
/**
136
-
* The {@link MathContext} to use for calculations.
137
-
*/
138
-
private MathContext mc;
139
-
140
-
/**
141
-
* The precedence of the power (^) operator. Default is 40.
142
-
*/
143
-
private int powerOperatorPrecedence = OPERATOR_PRECEDENCE_POWER;
144
-
145
-
/**
146
-
* The characters (other than letters and digits) allowed as the first character in a variable.
147
-
*/
148
-
private String firstVarChars = "_";
149
-
150
-
/**
151
-
* The characters (other than letters and digits) allowed as the second or subsequent characters
152
-
* in a variable.
153
-
*/
154
-
private String varChars = "_";
155
-
156
-
/**
157
-
* The original infix expression.
158
-
*/
159
-
private final String originalExpression;
160
-
161
-
/**
162
-
* The current infix expression, with optional variable substitutions.
163
-
*/
164
-
private String expressionString = null;
165
-
166
-
/**
167
-
* The cached RPN (Reverse Polish Notation) of the expression.
168
-
*/
169
-
private List<Token> rpn = null;
170
-
171
-
/**
172
-
* All defined operators with name and implementation.
173
-
*/
174
-
protected Map<String, LazyOperator> operators = new TreeMap<String, LazyOperator>(
175
-
String.CASE_INSENSITIVE_ORDER);
176
-
177
-
/**
178
-
* All defined functions with name and implementation.
179
-
*/
180
-
protected Map<String, com.udojava.evalex.LazyFunction> functions = new TreeMap<String, com.udojava.evalex.LazyFunction>(
181
-
String.CASE_INSENSITIVE_ORDER);
182
-
183
-
/**
184
-
* All defined variables with name and value.
185
-
*/
186
-
protected Map<String, LazyNumber> variables = new TreeMap<String, LazyNumber>(
187
-
String.CASE_INSENSITIVE_ORDER);
188
-
189
-
/**
190
-
* What character to use for decimal separators.
191
-
*/
192
-
private static final char DECIMAL_SEPARATOR = '.';
193
-
194
-
/**
195
-
* What character to use for minus sign (negative values).
196
-
*/
197
-
private static final char MINUS_SIGN = '-';
198
-
199
-
/**
200
-
* A map of symbols that stand for constant values. These are not considered variables by {@link #getUsedVariables()}.
201
-
*/
202
-
private static final Map<String, BigDecimal> DEFAULT_CONSTANTS = new HashMap<String, BigDecimal>();
203
-
static {
204
-
DEFAULT_CONSTANTS.put("e", e);
205
-
DEFAULT_CONSTANTS.put("PI", PI);
206
-
DEFAULT_CONSTANTS.put("NULL", null);
207
-
DEFAULT_CONSTANTS.put("TRUE", BigDecimal.ONE);
208
-
DEFAULT_CONSTANTS.put("FALSE", BigDecimal.ZERO);
209
-
}
210
-
211
-
/**
212
-
* The BigDecimal representation of the left parenthesis, used for parsing varying numbers of
213
-
* function parameters.
214
-
*/
215
-
private static final LazyNumber PARAMS_START = new LazyNumber() {
216
-
public BigDecimal eval() {
217
-
return null;
218
-
}
219
-
220
-
public String getString() {
221
-
return null;
222
-
}
223
-
};
224
-
225
-
226
-
/**
227
-
* The expression evaluators exception class.
228
-
*/
229
-
public static class ExpressionException extends RuntimeException {
230
-
231
-
private static final long serialVersionUID = 1118142866870779047L;
232
-
233
-
public ExpressionException(String message) {
234
-
super(message);
235
-
}
236
-
237
-
public ExpressionException(String message, int characterPosition) {
238
-
super(message + " at character position " + characterPosition);
239
-
}
240
-
}
241
-
242
-
243
-
/**
244
-
* LazyNumber interface created for lazily evaluated functions
245
-
*/
246
-
public interface LazyNumber {
247
-
248
-
BigDecimal eval();
249
-
250
-
String getString();
251
-
}
252
-
253
-
254
-
/**
255
-
* Construct a LazyNumber from a BigDecimal
256
-
*/
257
-
protected LazyNumber createLazyNumber(final BigDecimal bigDecimal) {
258
-
return new LazyNumber() {
259
-
@Override
260
-
public String getString() {
261
-
return bigDecimal.toPlainString();
262
-
}
263
-
264
-
@Override
265
-
public BigDecimal eval() {
266
-
return bigDecimal;
267
-
}
268
-
};
269
-
}
270
-
271
-
272
-
/**
273
-
* LazyFunction class
274
-
*/
275
-
public abstract class LazyFunction extends AbstractLazyFunction {
276
-
277
-
/**
278
-
* Creates a new function with given name and parameter count.
279
-
*
280
-
* @param name The name of the function.
281
-
* @param numParams The number of parameters for this function.
282
-
* <code>-1</code> denotes a variable number of parameters.
283
-
* @param booleanFunction Whether this function is a boolean function.
284
-
*/
285
-
public LazyFunction(String name, int numParams, boolean booleanFunction) {
286
-
super(name, numParams, booleanFunction);
287
-
}
288
-
289
-
/**
290
-
* Creates a new function with given name and parameter count.
291
-
*
292
-
* @param name The name of the function.
293
-
* @param numParams The number of parameters for this function.
294
-
* <code>-1</code> denotes a variable number of parameters.
295
-
*/
296
-
public LazyFunction(String name, int numParams) {
297
-
super(name, numParams);
298
-
}
299
-
}
300
-
301
-
302
-
/**
303
-
* Abstract definition of a supported expression function. A function is defined by a name, the
304
-
* number of parameters and the actual processing implementation.
305
-
*/
306
-
public abstract class Function extends AbstractFunction {
307
-
308
-
public Function(String name, int numParams) {
309
-
super(name, numParams);
310
-
}
311
-
312
-
public Function(String name, int numParams, boolean booleanFunction) {
313
-
super(name, numParams, booleanFunction);
314
-
}
315
-
}
316
-
317
-
318
-
/**
319
-
* Abstract definition of a supported operator. An operator is defined by its name (pattern),
320
-
* precedence, if it is left- or right-associative, if it is boolean and if it is unary (number of
321
-
* operands expected for the operation is 1 or 2).
322
-
*/
323
-
public abstract class Operator extends AbstractOperator {
324
-
325
-
/**
326
-
* Creates a new unary operator.
327
-
*
328
-
* @param oper The operator name (pattern).
329
-
* @param precedence The operators precedence.
330
-
* @param leftAssoc <code>true</code> if the operator is left associative,
331
-
* else <code>false</code>.
332
-
* @param booleanOperator Whether this operator is boolean.
333
-
* @param unaryOperator Whether this operator is unary.
334
-
*/
335
-
public Operator(String oper, int precedence, boolean leftAssoc, boolean booleanOperator,
336
-
boolean unaryOperator) {
337
-
super(oper, precedence, leftAssoc, booleanOperator, unaryOperator);
338
-
}
339
-
340
-
341
-
/**
342
-
* Creates a new boolean operator.
343
-
*
344
-
* @param oper The operator name (pattern).
345
-
* @param precedence The operators precedence.
346
-
* @param leftAssoc <code>true</code> if the operator is left associative,
347
-
* else <code>false</code>.
348
-
* @param booleanOperator Whether this operator is boolean.
349
-
*/
350
-
public Operator(String oper, int precedence, boolean leftAssoc, boolean booleanOperator) {
351
-
super(oper, precedence, leftAssoc, booleanOperator);
352
-
}
353
-
354
-
355
-
/**
356
-
* Creates a new operator.
357
-
*
358
-
* @param oper The operator name (pattern).
359
-
* @param precedence The operators precedence.
360
-
* @param leftAssoc <code>true</code> if the operator is left associative,
361
-
* else <code>false</code>.
362
-
*/
363
-
public Operator(String oper, int precedence, boolean leftAssoc) {
364
-
super(oper, precedence, leftAssoc);
365
-
}
366
-
}
367
-
368
-
369
-
/**
370
-
* Abstract definition of a prefix unary operator with right-side operand. It is defined by its
371
-
* name (pattern), precedence and if it is left- or right-associative. For this class, the
372
-
* operator is expected to have the single operand to its right.
373
-
*/
374
-
public abstract class UnaryOperator extends AbstractUnaryOperator {
375
-
376
-
public UnaryOperator(String oper, int precedence, boolean leftAssoc) {
377
-
super(oper, precedence, leftAssoc);
378
-
}
379
-
}
380
-
381
-
382
-
enum TokenType {
383
-
VARIABLE, FUNCTION, LITERAL, OPERATOR, UNARY_OPERATOR, OPEN_PAREN, COMMA, CLOSE_PAREN, HEX_LITERAL, STRINGPARAM
384
-
}
385
-
386
-
/**
387
-
* Token class
388
-
*/
389
-
public class Token {
390
-
391
-
public String surface = "";
392
-
public TokenType type;
393
-
public int pos;
394
-
395
-
public void append(char c) {
396
-
surface += c;
397
-
}
398
-
399
-
public void append(String s) {
400
-
surface += s;
401
-
}
402
-
403
-
public char charAt(int pos) {
404
-
return surface.charAt(pos);
405
-
}
406
-
407
-
public int length() {
408
-
return surface.length();
409
-
}
410
-
411
-
@Override
412
-
public String toString() {
413
-
return surface;
414
-
}
415
-
}
416
-
417
-
418
-
/**
419
-
* Expression tokenizer that allows to iterate over a {@link String} expression token by token.
420
-
* Blank characters will be skipped.
421
-
*/
422
-
private class Tokenizer implements Iterator<Token> {
423
-
424
-
/**
425
-
* Actual position in expression string.
426
-
*/
427
-
private int pos = 0;
428
-
429
-
/**
430
-
* The original input expression.
431
-
*/
432
-
private String input;
433
-
/**
434
-
* The previous token or <code>null</code> if none.
435
-
*/
436
-
private Token previousToken;
437
-
438
-
/**
439
-
* The next token or <code>null</code> if none.
440
-
*/
441
-
private Token nextToken = new Token();
442
-
443
-
/**
444
-
* Creates a new tokenizer for an expression.
445
-
*
446
-
* @param input The expression string.
447
-
*/
448
-
public Tokenizer(String input) {
449
-
this.input = input.trim();
450
-
}
451
-
452
-
@Override
453
-
public boolean hasNext() {
454
-
return (pos < input.length());
455
-
}
456
-
457
-
/**
458
-
* Peek at the next character, without advancing the iterator.
459
-
*
460
-
* @return The next character or character 0, if at end of string.
461
-
*/
462
-
private char peekNextChar() {
463
-
return pos >= input.length()-1 ? 0 : input.charAt(pos+1);
464
-
}
465
-
466
-
/**
467
-
* Append the current character to the given token, and advance the tokenizer position.
468
-
*
469
-
* @param token the destination token for the character
470
-
* @return The next character or character 0, if at end of string.
471
-
*/
472
-
private char consumeChar(Token token) {
473
-
token.append(input.charAt(pos++));
474
-
return pos >= input.length() ? 0 : input.charAt(pos);
475
-
}
476
-
477
-
/**
478
-
* Discard the current character, and advance the tokenizer position by 1.
479
-
*
480
-
* @return The next character or character 0, if at end of string.
481
-
*/
482
-
private char skipChar() {
483
-
pos++;
484
-
return pos >= input.length() ? 0 : input.charAt(pos);
485
-
}
486
-
487
-
private boolean isHexDigit(char ch) {
488
-
return ch == 'x' || ch == 'X' || (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f')
489
-
|| (ch >= 'A' && ch <= 'F');
490
-
}
491
-
492
-
@Override
493
-
public Token next() {
494
-
Token token = new Token();
495
-
496
-
if (pos >= input.length()) {
497
-
previousToken = null;
498
-
return null;
499
-
}
500
-
char ch = input.charAt(pos);
501
-
while (Character.isWhitespace(ch) && pos < input.length()) {
502
-
ch = skipChar();
503
-
}
504
-
token.pos = pos;
505
-
506
-
if (pos < input.length() - 1) {
507
-
nextToken.pos = pos + 1;
508
-
} else {
509
-
nextToken = null;
510
-
}
511
-
512
-
boolean isHex = false;
513
-
514
-
if (Character.isDigit(ch) || (ch == DECIMAL_SEPARATOR && Character.isDigit(peekNextChar()))) {
515
-
if (ch == '0' && (peekNextChar() == 'x' || peekNextChar() == 'X')) {
516
-
isHex = true;
517
-
}
518
-
while ((isHex
519
-
&& isHexDigit(
520
-
ch))
521
-
|| (Character.isDigit(ch) || ch == DECIMAL_SEPARATOR || ch == 'e' || ch == 'E'
522
-
|| (ch == MINUS_SIGN && token.length() > 0
523
-
&& ('e' == token.charAt(token.length() - 1)
524
-
|| 'E' == token.charAt(token.length() - 1)))
525
-
|| (ch == '+' && token.length() > 0
526
-
&& ('e' == token.charAt(token.length() - 1)
527
-
|| 'E' == token.charAt(token.length() - 1))))
528
-
&& (pos < input.length())) {
529
-
ch = consumeChar(token);
530
-
}
531
-
token.type = isHex ? TokenType.HEX_LITERAL : TokenType.LITERAL;
532
-
} else if (ch == '"') {
533
-
tokenizeString(token);
534
-
} else if (Character.isLetter(ch) || firstVarChars.indexOf(ch) >= 0) {
535
-
while ((Character.isLetter(ch) || Character.isDigit(ch) || varChars.indexOf(ch) >= 0
536
-
|| token.length() == 0 && firstVarChars.indexOf(ch) >= 0) && (pos < input.length())) {
537
-
ch = consumeChar(token);
538
-
}
539
-
// Remove optional white spaces after function or variable name
540
-
if (Character.isWhitespace(ch)) {
541
-
while (Character.isWhitespace(ch) && pos < input.length()) {
542
-
ch = skipChar();
543
-
}
544
-
pos--;
545
-
}
546
-
if (operators.containsKey(token.surface)) {
547
-
token.type = TokenType.OPERATOR;
548
-
} else if (ch == '(') {
549
-
token.type = TokenType.FUNCTION;
550
-
} else {
551
-
token.type = TokenType.VARIABLE;
552
-
}
553
-
} else if (ch == '(' || ch == ')' || ch == ',') {
554
-
if (ch == '(') {
555
-
token.type = TokenType.OPEN_PAREN;
556
-
} else if (ch == ')') {
557
-
token.type = TokenType.CLOSE_PAREN;
558
-
} else {
559
-
token.type = TokenType.COMMA;
560
-
}
561
-
consumeChar(token);
562
-
} else {
563
-
String greedyMatch = "";
564
-
int initialPos = pos;
565
-
ch = input.charAt(pos);
566
-
int validOperatorSeenUntil = -1;
567
-
while (!Character.isLetter(ch) && !Character.isDigit(ch) && firstVarChars.indexOf(ch) < 0
568
-
&& !Character.isWhitespace(ch) && ch != '(' && ch != ')' && ch != ','
569
-
&& (pos < input.length())) {
570
-
greedyMatch += ch;
571
-
ch = skipChar();
572
-
if (operators.containsKey(greedyMatch)) {
573
-
validOperatorSeenUntil = pos;
574
-
}
575
-
}
576
-
if (validOperatorSeenUntil != -1) {
577
-
token.append(input.substring(initialPos, validOperatorSeenUntil));
578
-
pos = validOperatorSeenUntil;
579
-
} else {
580
-
token.append(greedyMatch);
581
-
}
582
-
/* If the previousToken is of TokenType.OPERATOR, its parameter unaryOperator must be <code>false</code> to
583
-
* accept TokenType.UNARY_OPERATOR for the current token
584
-
*/
585
-
if (previousToken == null || (previousToken.type == TokenType.OPERATOR && !operators
586
-
.get(previousToken.surface).isUnaryOperator())
587
-
|| previousToken.type == TokenType.OPEN_PAREN || previousToken.type == TokenType.COMMA
588
-
|| previousToken.type == TokenType.UNARY_OPERATOR) {
589
-
token.surface += "u";
590
-
token.type = TokenType.UNARY_OPERATOR;
591
-
} else {
592
-
token.type = TokenType.OPERATOR;
593
-
}
594
-
}
595
-
previousToken = token;
596
-
return token;
597
-
}
598
-
599
-
private void tokenizeString(Token token) {
600
-
char ch;
601
-
// skip opening quote
602
-
ch = skipChar();
603
-
604
-
// consume string contents
605
-
while (ch != '"' && ch != 0) {
606
-
ch = consumeChar(token);
607
-
}
608
-
609
-
// deal with the closing quote
610
-
if (ch == 0) {
611
-
throw new TokenizerException("unterminated string literal", token.pos);
612
-
}
613
-
skipChar();
614
-
615
-
token.type = TokenType.STRINGPARAM;
616
-
}
617
-
618
-
@Override
619
-
public void remove() {
620
-
throw new ExpressionException("remove() not supported");
621
-
}
622
-
623
-
}
624
-
625
-
626
-
/**
627
-
* Creates a new expression instance from an expression string with a given default match context
628
-
* of {@link MathContext#DECIMAL32}.
629
-
*
630
-
* @param expression The expression. E.g. <code>"2.4*sin(3)/(2-4)"</code> or
631
-
* <code>"sin(y)>0 & max(z, 3)>3"</code>
632
-
*/
633
-
public Expression(String expression) {
634
-
this(expression, MathContext.DECIMAL32);
635
-
}
636
-
637
-
638
-
/**
639
-
* Creates a new expression instance from an expression string with a given default match
640
-
* context.
641
-
*
642
-
* @param expression The expression. E.g. <code>"2.4*sin(3)/(2-4)"</code> or
643
-
* <code>"sin(y)>0 & max(z, 3)>3"</code>
644
-
* @param defaultMathContext The {@link MathContext} to use by default.
645
-
*/
646
-
public Expression(String expression, MathContext defaultMathContext) {
647
-
this(expression, ExpressionSettings
648
-
.builder()
649
-
.mathContext(defaultMathContext)
650
-
.build());
651
-
}
652
-
653
-
/**
654
-
* Creates a new expression instance from an expression string with given settings.
655
-
*
656
-
* @param expression The expression. E.g. <code>"2.4*sin(3)/(2-4)"</code> or
657
-
* <code>"sin(y)>0 & max(z, 3)>3"</code>
658
-
* @param expressionSettings The {@link ExpressionSettings} to use by default.
659
-
*/
660
-
public Expression(String expression,
661
-
ExpressionSettings expressionSettings) { // NOSONAR- cognitive complexity
662
-
this.mc = expressionSettings.getMathContext();
663
-
this.powerOperatorPrecedence = expressionSettings.getPowerOperatorPrecedence();
664
-
this.expressionString = expression;
665
-
this.originalExpression = expression;
666
-
addOperator(new Operator("+", OPERATOR_PRECEDENCE_ADDITIVE, true) {
667
-
@Override
668
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
669
-
assertNotNull(v1, v2);
670
-
return v1.add(v2, mc);
671
-
}
672
-
});
673
-
addOperator(new Operator("-", OPERATOR_PRECEDENCE_ADDITIVE, true) {
674
-
@Override
675
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
676
-
assertNotNull(v1, v2);
677
-
return v1.subtract(v2, mc);
678
-
}
679
-
});
680
-
addOperator(new Operator("*", OPERATOR_PRECEDENCE_MULTIPLICATIVE, true) {
681
-
@Override
682
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
683
-
assertNotNull(v1, v2);
684
-
return v1.multiply(v2, mc);
685
-
}
686
-
});
687
-
addOperator(new Operator("/", OPERATOR_PRECEDENCE_MULTIPLICATIVE, true) {
688
-
@Override
689
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
690
-
assertNotNull(v1, v2);
691
-
return v1.divide(v2, mc);
692
-
}
693
-
});
694
-
addOperator(new Operator("%", OPERATOR_PRECEDENCE_MULTIPLICATIVE, true) {
695
-
@Override
696
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
697
-
assertNotNull(v1, v2);
698
-
return v1.remainder(v2, mc);
699
-
}
700
-
});
701
-
addOperator(new Operator("^", powerOperatorPrecedence, false) {
702
-
@Override
703
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
704
-
assertNotNull(v1, v2);
705
-
/*-
706
-
* Thanks to Gene Marin:
707
-
* http://stackoverflow.com/questions/3579779/how-to-do-a-fractional-power-on-bigdecimal-in-java
708
-
*/
709
-
int signOf2 = v2.signum();
710
-
double dn1 = v1.doubleValue();
711
-
v2 = v2.multiply(new BigDecimal(signOf2)); // n2 is now positive
712
-
BigDecimal remainderOf2 = v2.remainder(BigDecimal.ONE);
713
-
BigDecimal n2IntPart = v2.subtract(remainderOf2);
714
-
BigDecimal intPow = v1.pow(n2IntPart.intValueExact(), mc);
715
-
BigDecimal doublePow = BigDecimal.valueOf(Math.pow(dn1, remainderOf2.doubleValue()));
716
-
717
-
BigDecimal result = intPow.multiply(doublePow, mc);
718
-
if (signOf2 == -1) {
719
-
result = BigDecimal.ONE.divide(result, mc.getPrecision(), RoundingMode.HALF_UP);
720
-
}
721
-
return result;
722
-
}
723
-
});
724
-
addOperator(new Operator("&&", OPERATOR_PRECEDENCE_AND, false, true) {
725
-
@Override
726
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
727
-
assertNotNull(v1, v2);
728
-
729
-
boolean b1 = v1.compareTo(BigDecimal.ZERO) != 0;
730
-
731
-
if (!b1) {
732
-
return BigDecimal.ZERO;
733
-
}
734
-
735
-
boolean b2 = v2.compareTo(BigDecimal.ZERO) != 0;
736
-
return b2 ? BigDecimal.ONE : BigDecimal.ZERO;
737
-
}
738
-
});
739
-
740
-
addOperator(new Operator("||", OPERATOR_PRECEDENCE_OR, false, true) {
741
-
@Override
742
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
743
-
assertNotNull(v1, v2);
744
-
745
-
boolean b1 = v1.compareTo(BigDecimal.ZERO) != 0;
746
-
747
-
if (b1) {
748
-
return BigDecimal.ONE;
749
-
}
750
-
751
-
boolean b2 = v2.compareTo(BigDecimal.ZERO) != 0;
752
-
return b2 ? BigDecimal.ONE : BigDecimal.ZERO;
753
-
}
754
-
});
755
-
756
-
addOperator(new Operator(">", OPERATOR_PRECEDENCE_COMPARISON, false, true) {
757
-
@Override
758
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
759
-
assertNotNull(v1, v2);
760
-
return v1.compareTo(v2) == 1 ? BigDecimal.ONE : BigDecimal.ZERO;
761
-
}
762
-
});
763
-
764
-
addOperator(new Operator(">=", OPERATOR_PRECEDENCE_COMPARISON, false, true) {
765
-
@Override
766
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
767
-
assertNotNull(v1, v2);
768
-
return v1.compareTo(v2) >= 0 ? BigDecimal.ONE : BigDecimal.ZERO;
769
-
}
770
-
});
771
-
772
-
addOperator(new Operator("<", OPERATOR_PRECEDENCE_COMPARISON, false, true) {
773
-
@Override
774
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
775
-
assertNotNull(v1, v2);
776
-
return v1.compareTo(v2) == -1 ? BigDecimal.ONE : BigDecimal.ZERO;
777
-
}
778
-
});
779
-
780
-
addOperator(new Operator("<=", OPERATOR_PRECEDENCE_COMPARISON, false, true) {
781
-
@Override
782
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
783
-
assertNotNull(v1, v2);
784
-
return v1.compareTo(v2) <= 0 ? BigDecimal.ONE : BigDecimal.ZERO;
785
-
}
786
-
});
787
-
788
-
addOperator(new Operator("=", OPERATOR_PRECEDENCE_EQUALITY, false, true) {
789
-
@Override
790
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
791
-
if (v1 == v2) {
792
-
return BigDecimal.ONE;
793
-
}
794
-
if (v1 == null || v2 == null) {
795
-
return BigDecimal.ZERO;
796
-
}
797
-
return v1.compareTo(v2) == 0 ? BigDecimal.ONE : BigDecimal.ZERO;
798
-
}
799
-
});
800
-
addOperator(new Operator("==", OPERATOR_PRECEDENCE_EQUALITY, false, true) {
801
-
@Override
802
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
803
-
return ((Operator) operators.get("=")).eval(v1, v2);
804
-
}
805
-
});
806
-
807
-
addOperator(new Operator("!=", OPERATOR_PRECEDENCE_EQUALITY, false, true) {
808
-
@Override
809
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
810
-
if (v1 == v2) {
811
-
return BigDecimal.ZERO;
812
-
}
813
-
if (v1 == null || v2 == null) {
814
-
return BigDecimal.ONE;
815
-
}
816
-
return v1.compareTo(v2) != 0 ? BigDecimal.ONE : BigDecimal.ZERO;
817
-
}
818
-
});
819
-
addOperator(new Operator("<>", OPERATOR_PRECEDENCE_EQUALITY, false, true) {
820
-
@Override
821
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
822
-
assertNotNull(v1, v2);
823
-
return ((Operator) operators.get("!=")).eval(v1, v2);
824
-
}
825
-
});
826
-
827
-
addOperator(new UnaryOperator("-", OPERATOR_PRECEDENCE_UNARY, false) {
828
-
@Override
829
-
public BigDecimal evalUnary(BigDecimal v1) {
830
-
return v1.multiply(new BigDecimal(-1));
831
-
}
832
-
});
833
-
addOperator(new UnaryOperator("+", OPERATOR_PRECEDENCE_UNARY, false) {
834
-
@Override
835
-
public BigDecimal evalUnary(BigDecimal v1) {
836
-
return v1.multiply(BigDecimal.ONE);
837
-
}
838
-
});
839
-
840
-
addFunction(new Function("FACT", 1, false) {
841
-
@Override
842
-
public BigDecimal eval(List<BigDecimal> parameters) {
843
-
assertNotNull(parameters.get(0));
844
-
845
-
int number = parameters.get(0).intValue();
846
-
BigDecimal factorial = BigDecimal.ONE;
847
-
for (int i = 1; i <= number; i++) {
848
-
factorial = factorial.multiply(new BigDecimal(i));
849
-
}
850
-
return factorial;
851
-
}
852
-
});
853
-
854
-
addFunction(new Function("NOT", 1, true) {
855
-
@Override
856
-
public BigDecimal eval(List<BigDecimal> parameters) {
857
-
assertNotNull(parameters.get(0));
858
-
boolean zero = parameters.get(0).compareTo(BigDecimal.ZERO) == 0;
859
-
return zero ? BigDecimal.ONE : BigDecimal.ZERO;
860
-
}
861
-
});
862
-
863
-
addLazyFunction(new LazyFunction("IF", 3) {
864
-
@Override
865
-
public LazyNumber lazyEval(List<LazyNumber> lazyParams) {
866
-
return new LazyIfNumber(lazyParams);
867
-
}
868
-
});
869
-
870
-
addFunction(new Function("RANDOM", 0) {
871
-
@Override
872
-
public BigDecimal eval(List<BigDecimal> parameters) {
873
-
double d = Math.random();
874
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
875
-
}
876
-
});
877
-
addFunction(new Function("SINR", 1) {
878
-
@Override
879
-
public BigDecimal eval(List<BigDecimal> parameters) {
880
-
assertNotNull(parameters.get(0));
881
-
double d = Math.sin(parameters.get(0).doubleValue());
882
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
883
-
}
884
-
});
885
-
addFunction(new Function("COSR", 1) {
886
-
@Override
887
-
public BigDecimal eval(List<BigDecimal> parameters) {
888
-
assertNotNull(parameters.get(0));
889
-
double d = Math.cos(parameters.get(0).doubleValue());
890
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
891
-
}
892
-
});
893
-
addFunction(new Function("TANR", 1) {
894
-
@Override
895
-
public BigDecimal eval(List<BigDecimal> parameters) {
896
-
assertNotNull(parameters.get(0));
897
-
double d = Math.tan(parameters.get(0).doubleValue());
898
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
899
-
}
900
-
});
901
-
addFunction(new Function("COTR", 1) {
902
-
@Override
903
-
public BigDecimal eval(List<BigDecimal> parameters) {
904
-
assertNotNull(parameters.get(0));
905
-
/* Formula: cot(x) = cos(x) / sin(x) = 1 / tan(x) */
906
-
double one = 1;
907
-
double d = Math.tan(parameters.get(0).doubleValue());
908
-
return new BigDecimal((one / d), mc); // NOSONAR - false positive, mc is passed
909
-
}
910
-
});
911
-
addFunction(new Function("SECR", 1) {
912
-
@Override
913
-
public BigDecimal eval(List<BigDecimal> parameters) {
914
-
assertNotNull(parameters.get(0));
915
-
/* Formula: sec(x) = 1 / cos(x) */
916
-
double one = 1;
917
-
double d = Math.cos(parameters.get(0).doubleValue());
918
-
return new BigDecimal((one / d), mc); // NOSONAR - false positive, mc is passed
919
-
}
920
-
});
921
-
addFunction(new Function("CSCR", 1) {
922
-
@Override
923
-
public BigDecimal eval(List<BigDecimal> parameters) {
924
-
assertNotNull(parameters.get(0));
925
-
/* Formula: csc(x) = 1 / sin(x) */
926
-
double one = 1;
927
-
double d = Math.sin(parameters.get(0).doubleValue());
928
-
return new BigDecimal((one / d), mc); // NOSONAR - false positive, mc is passed
929
-
}
930
-
});
931
-
addFunction(new Function("SIN", 1) {
932
-
@Override
933
-
public BigDecimal eval(List<BigDecimal> parameters) {
934
-
assertNotNull(parameters.get(0));
935
-
double d = Math.sin(Math.toRadians(parameters.get(0).doubleValue()));
936
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
937
-
}
938
-
});
939
-
addFunction(new Function("COS", 1) {
940
-
@Override
941
-
public BigDecimal eval(List<BigDecimal> parameters) {
942
-
assertNotNull(parameters.get(0));
943
-
double d = Math.cos(Math.toRadians(parameters.get(0).doubleValue()));
944
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
945
-
}
946
-
});
947
-
addFunction(new Function("TAN", 1) {
948
-
@Override
949
-
public BigDecimal eval(List<BigDecimal> parameters) {
950
-
assertNotNull(parameters.get(0));
951
-
double d = Math.tan(Math.toRadians(parameters.get(0).doubleValue()));
952
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
953
-
}
954
-
});
955
-
addFunction(new Function("COT", 1) {
956
-
@Override
957
-
public BigDecimal eval(List<BigDecimal> parameters) {
958
-
assertNotNull(parameters.get(0));
959
-
/* Formula: cot(x) = cos(x) / sin(x) = 1 / tan(x) */
960
-
double one = 1;
961
-
double d = Math.tan(Math.toRadians(parameters.get(0).doubleValue()));
962
-
return new BigDecimal((one / d), mc); // NOSONAR - false positive, mc is passed
963
-
}
964
-
});
965
-
addFunction(new Function("SEC", 1) {
966
-
@Override
967
-
public BigDecimal eval(List<BigDecimal> parameters) {
968
-
assertNotNull(parameters.get(0));
969
-
/* Formula: sec(x) = 1 / cos(x) */
970
-
double one = 1;
971
-
double d = Math.cos(Math.toRadians(parameters.get(0).doubleValue()));
972
-
return new BigDecimal((one / d), mc); // NOSONAR - false positive, mc is passed
973
-
}
974
-
});
975
-
addFunction(new Function("CSC", 1) {
976
-
@Override
977
-
public BigDecimal eval(List<BigDecimal> parameters) {
978
-
assertNotNull(parameters.get(0));
979
-
/* Formula: csc(x) = 1 / sin(x) */
980
-
double one = 1;
981
-
double d = Math.sin(Math.toRadians(parameters.get(0).doubleValue()));
982
-
return new BigDecimal((one / d), mc); // NOSONAR - false positive, mc is passed
983
-
}
984
-
});
985
-
addFunction(new Function("ASINR", 1) {
986
-
@Override
987
-
public BigDecimal eval(List<BigDecimal> parameters) {
988
-
assertNotNull(parameters.get(0));
989
-
double d = Math.asin(parameters.get(0).doubleValue());
990
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
991
-
}
992
-
});
993
-
addFunction(new Function("ACOSR", 1) {
994
-
@Override
995
-
public BigDecimal eval(List<BigDecimal> parameters) {
996
-
assertNotNull(parameters.get(0));
997
-
double d = Math.acos(parameters.get(0).doubleValue());
998
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
999
-
}
1000
-
});
1001
-
addFunction(new Function("ATANR", 1) {
1002
-
@Override
1003
-
public BigDecimal eval(List<BigDecimal> parameters) {
1004
-
assertNotNull(parameters.get(0));
1005
-
double d = Math.atan(parameters.get(0).doubleValue());
1006
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
1007
-
}
1008
-
});
1009
-
addFunction(new Function("ACOTR", 1) {
1010
-
@Override
1011
-
public BigDecimal eval(List<BigDecimal> parameters) {
1012
-
assertNotNull(parameters.get(0));
1013
-
/* Formula: acot(x) = atan(1/x) */
1014
-
if (parameters.get(0).doubleValue() == 0) {
1015
-
throw new ExpressionException("Number must not be 0");
1016
-
}
1017
-
double d = Math.atan(1 / parameters.get(0).doubleValue());
1018
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
1019
-
}
1020
-
});
1021
-
addFunction(new Function("ATAN2R", 2) {
1022
-
@Override
1023
-
public BigDecimal eval(List<BigDecimal> parameters) {
1024
-
assertNotNull(parameters.get(0), parameters.get(1));
1025
-
double d = Math.atan2(parameters.get(0).doubleValue(), parameters.get(1).doubleValue());
1026
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
1027
-
}
1028
-
});
1029
-
addFunction(new Function("ASIN", 1) {
1030
-
@Override
1031
-
public BigDecimal eval(List<BigDecimal> parameters) {
1032
-
assertNotNull(parameters.get(0));
1033
-
double d = Math.toDegrees(Math.asin(parameters.get(0).doubleValue()));
1034
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
1035
-
}
1036
-
});
1037
-
addFunction(new Function("ACOS", 1) {
1038
-
@Override
1039
-
public BigDecimal eval(List<BigDecimal> parameters) {
1040
-
assertNotNull(parameters.get(0));
1041
-
double d = Math.toDegrees(Math.acos(parameters.get(0).doubleValue()));
1042
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
1043
-
}
1044
-
});
1045
-
addFunction(new Function("ATAN", 1) {
1046
-
@Override
1047
-
public BigDecimal eval(List<BigDecimal> parameters) {
1048
-
assertNotNull(parameters.get(0));
1049
-
double d = Math.toDegrees(Math.atan(parameters.get(0).doubleValue()));
1050
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
1051
-
}
1052
-
});
1053
-
addFunction(new Function("ACOT", 1) {
1054
-
@Override
1055
-
public BigDecimal eval(List<BigDecimal> parameters) {
1056
-
assertNotNull(parameters.get(0));
1057
-
/* Formula: acot(x) = atan(1/x) */
1058
-
if (parameters.get(0).doubleValue() == 0) {
1059
-
throw new ExpressionException("Number must not be 0");
1060
-
}
1061
-
double d = Math.toDegrees(Math.atan(1 / parameters.get(0).doubleValue()));
1062
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
1063
-
}
1064
-
});
1065
-
addFunction(new Function("ATAN2", 2) {
1066
-
@Override
1067
-
public BigDecimal eval(List<BigDecimal> parameters) {
1068
-
assertNotNull(parameters.get(0), parameters.get(1));
1069
-
double d = Math.toDegrees(
1070
-
Math.atan2(parameters.get(0).doubleValue(), parameters.get(1).doubleValue()));
1071
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
1072
-
}
1073
-
});
1074
-
addFunction(new Function("SINH", 1) {
1075
-
@Override
1076
-
public BigDecimal eval(List<BigDecimal> parameters) {
1077
-
assertNotNull(parameters.get(0));
1078
-
double d = Math.sinh(parameters.get(0).doubleValue());
1079
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
1080
-
}
1081
-
});
1082
-
addFunction(new Function("COSH", 1) {
1083
-
@Override
1084
-
public BigDecimal eval(List<BigDecimal> parameters) {
1085
-
assertNotNull(parameters.get(0));
1086
-
double d = Math.cosh(parameters.get(0).doubleValue());
1087
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
1088
-
}
1089
-
});
1090
-
addFunction(new Function("TANH", 1) {
1091
-
@Override
1092
-
public BigDecimal eval(List<BigDecimal> parameters) {
1093
-
assertNotNull(parameters.get(0));
1094
-
double d = Math.tanh(parameters.get(0).doubleValue());
1095
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
1096
-
}
1097
-
});
1098
-
addFunction(new Function("SECH", 1) {
1099
-
@Override
1100
-
public BigDecimal eval(List<BigDecimal> parameters) {
1101
-
assertNotNull(parameters.get(0));
1102
-
/* Formula: sech(x) = 1 / cosh(x) */
1103
-
double one = 1;
1104
-
double d = Math.cosh(parameters.get(0).doubleValue());
1105
-
return new BigDecimal((one / d), mc); // NOSONAR - false positive, mc is passed
1106
-
}
1107
-
});
1108
-
addFunction(new Function("CSCH", 1) {
1109
-
@Override
1110
-
public BigDecimal eval(List<BigDecimal> parameters) {
1111
-
assertNotNull(parameters.get(0));
1112
-
/* Formula: csch(x) = 1 / sinh(x) */
1113
-
double one = 1;
1114
-
double d = Math.sinh(parameters.get(0).doubleValue());
1115
-
return new BigDecimal((one / d), mc); // NOSONAR - false positive, mc is passed
1116
-
}
1117
-
});
1118
-
addFunction(new Function("COTH", 1) {
1119
-
@Override
1120
-
public BigDecimal eval(List<BigDecimal> parameters) {
1121
-
assertNotNull(parameters.get(0));
1122
-
/* Formula: coth(x) = 1 / tanh(x) */
1123
-
double one = 1;
1124
-
double d = Math.tanh(parameters.get(0).doubleValue());
1125
-
return new BigDecimal((one / d), mc); // NOSONAR - false positive, mc is passed
1126
-
}
1127
-
});
1128
-
addFunction(new Function("ASINH", 1) {
1129
-
@Override
1130
-
public BigDecimal eval(List<BigDecimal> parameters) {
1131
-
assertNotNull(parameters.get(0));
1132
-
/* Formula: asinh(x) = ln(x + sqrt(x^2 + 1)) */
1133
-
double d = Math.log(parameters.get(0).doubleValue()
1134
-
+ (Math.sqrt(Math.pow(parameters.get(0).doubleValue(), 2) + 1)));
1135
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
1136
-
}
1137
-
});
1138
-
addFunction(new Function("ACOSH", 1) {
1139
-
@Override
1140
-
public BigDecimal eval(List<BigDecimal> parameters) {
1141
-
assertNotNull(parameters.get(0));
1142
-
/* Formula: acosh(x) = ln(x + sqrt(x^2 - 1)) */
1143
-
if (Double.compare(parameters.get(0).doubleValue(), 1) < 0) {
1144
-
throw new ExpressionException("Number must be x >= 1");
1145
-
}
1146
-
double d = Math.log(parameters.get(0).doubleValue()
1147
-
+ (Math.sqrt(Math.pow(parameters.get(0).doubleValue(), 2) - 1)));
1148
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
1149
-
}
1150
-
});
1151
-
addFunction(new Function("ATANH", 1) {
1152
-
@Override
1153
-
public BigDecimal eval(List<BigDecimal> parameters) {
1154
-
assertNotNull(parameters.get(0));
1155
-
/* Formula: atanh(x) = 0.5*ln((1 + x)/(1 - x)) */
1156
-
if (Math.abs(parameters.get(0).doubleValue()) > 1
1157
-
|| Math.abs(parameters.get(0).doubleValue()) == 1) {
1158
-
throw new ExpressionException("Number must be |x| < 1");
1159
-
}
1160
-
double d = 0.5
1161
-
* Math
1162
-
.log((1 + parameters.get(0).doubleValue()) / (1 - parameters.get(0).doubleValue()));
1163
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
1164
-
}
1165
-
});
1166
-
addFunction(new Function("RAD", 1) {
1167
-
@Override
1168
-
public BigDecimal eval(List<BigDecimal> parameters) {
1169
-
assertNotNull(parameters.get(0));
1170
-
double d = Math.toRadians(parameters.get(0).doubleValue());
1171
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
1172
-
}
1173
-
});
1174
-
addFunction(new Function("DEG", 1) {
1175
-
@Override
1176
-
public BigDecimal eval(List<BigDecimal> parameters) {
1177
-
assertNotNull(parameters.get(0));
1178
-
double d = Math.toDegrees(parameters.get(0).doubleValue());
1179
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
1180
-
}
1181
-
});
1182
-
addFunction(new Function("MAX", -1) {
1183
-
@Override
1184
-
public BigDecimal eval(List<BigDecimal> parameters) {
1185
-
if (parameters.isEmpty()) {
1186
-
throw new ExpressionException("MAX requires at least one parameter");
1187
-
}
1188
-
BigDecimal max = null;
1189
-
for (BigDecimal parameter : parameters) {
1190
-
assertNotNull(parameter);
1191
-
if (max == null || parameter.compareTo(max) > 0) {
1192
-
max = parameter;
1193
-
}
1194
-
}
1195
-
return max;
1196
-
}
1197
-
});
1198
-
addFunction(new Function("MIN", -1) {
1199
-
@Override
1200
-
public BigDecimal eval(List<BigDecimal> parameters) {
1201
-
if (parameters.isEmpty()) {
1202
-
throw new ExpressionException("MIN requires at least one parameter");
1203
-
}
1204
-
BigDecimal min = null;
1205
-
for (BigDecimal parameter : parameters) {
1206
-
assertNotNull(parameter);
1207
-
if (min == null || parameter.compareTo(min) < 0) {
1208
-
min = parameter;
1209
-
}
1210
-
}
1211
-
return min;
1212
-
}
1213
-
});
1214
-
addFunction(new Function("ABS", 1) {
1215
-
@Override
1216
-
public BigDecimal eval(List<BigDecimal> parameters) {
1217
-
assertNotNull(parameters.get(0));
1218
-
return parameters.get(0).abs(mc);
1219
-
}
1220
-
});
1221
-
addFunction(new Function("LOG", 1) {
1222
-
@Override
1223
-
public BigDecimal eval(List<BigDecimal> parameters) {
1224
-
assertNotNull(parameters.get(0));
1225
-
double d = Math.log(parameters.get(0).doubleValue());
1226
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
1227
-
}
1228
-
});
1229
-
addFunction(new Function("LOG10", 1) {
1230
-
@Override
1231
-
public BigDecimal eval(List<BigDecimal> parameters) {
1232
-
assertNotNull(parameters.get(0));
1233
-
double d = Math.log10(parameters.get(0).doubleValue());
1234
-
return new BigDecimal(d, mc); // NOSONAR - false positive, mc is passed
1235
-
}
1236
-
});
1237
-
addFunction(new Function("ROUND", 2) {
1238
-
@Override
1239
-
public BigDecimal eval(List<BigDecimal> parameters) {
1240
-
assertNotNull(parameters.get(0), parameters.get(1));
1241
-
BigDecimal toRound = parameters.get(0);
1242
-
int precision = parameters.get(1).intValue();
1243
-
return toRound.setScale(precision, mc.getRoundingMode());
1244
-
}
1245
-
});
1246
-
addFunction(new Function("FLOOR", 1) {
1247
-
@Override
1248
-
public BigDecimal eval(List<BigDecimal> parameters) {
1249
-
assertNotNull(parameters.get(0));
1250
-
BigDecimal toRound = parameters.get(0);
1251
-
return toRound.setScale(0, RoundingMode.FLOOR);
1252
-
}
1253
-
});
1254
-
addFunction(new Function("CEILING", 1) {
1255
-
@Override
1256
-
public BigDecimal eval(List<BigDecimal> parameters) {
1257
-
assertNotNull(parameters.get(0));
1258
-
BigDecimal toRound = parameters.get(0);
1259
-
return toRound.setScale(0, RoundingMode.CEILING);
1260
-
}
1261
-
});
1262
-
addFunction(new Function("SQRT", 1) {
1263
-
@Override
1264
-
public BigDecimal eval(List<BigDecimal> parameters) {
1265
-
assertNotNull(parameters.get(0));
1266
-
/*
1267
-
* From The Java Programmers Guide To numerical Computing
1268
-
* (Ronald Mak, 2003)
1269
-
*/
1270
-
BigDecimal x = parameters.get(0);
1271
-
if (x.compareTo(BigDecimal.ZERO) == 0) {
1272
-
return new BigDecimal(0);
1273
-
}
1274
-
if (x.signum() < 0) {
1275
-
throw new ExpressionException("Argument to SQRT() function must not be negative");
1276
-
}
1277
-
BigInteger n = x.movePointRight(mc.getPrecision() << 1).toBigInteger();
1278
-
1279
-
int bits = (n.bitLength() + 1) >> 1;
1280
-
BigInteger ix = n.shiftRight(bits);
1281
-
BigInteger ixPrev;
1282
-
BigInteger test;
1283
-
do {
1284
-
ixPrev = ix;
1285
-
ix = ix.add(n.divide(ix)).shiftRight(1);
1286
-
// Give other threads a chance to work
1287
-
Thread.yield();
1288
-
test = ix.subtract(ixPrev).abs();
1289
-
} while (test.compareTo(BigInteger.ZERO) != 0 && test.compareTo(BigInteger.ONE) != 0);
1290
-
1291
-
return new BigDecimal(ix, mc.getPrecision());
1292
-
}
1293
-
});
1294
-
1295
-
for (Map.Entry<String, BigDecimal> constant : DEFAULT_CONSTANTS.entrySet()) {
1296
-
BigDecimal value = constant.getValue();
1297
-
variables.put(constant.getKey(), value == null? null : createLazyNumber(value));
1298
-
}
1299
-
}
1300
-
1301
-
1302
-
/**
1303
-
* Assert the single expected operand is not null.
1304
-
*/
1305
-
public static void assertNotNull(BigDecimal v1) {
1306
-
if (v1 == null) {
1307
-
throw new ArithmeticException("Operand may not be null");
1308
-
}
1309
-
}
1310
-
1311
-
1312
-
/**
1313
-
* Assert the two expected operands are not null.
1314
-
*/
1315
-
public static void assertNotNull(BigDecimal v1, BigDecimal v2) {
1316
-
if (v1 == null) {
1317
-
throw new ArithmeticException("First operand may not be null");
1318
-
}
1319
-
if (v2 == null) {
1320
-
throw new ArithmeticException("Second operand may not be null");
1321
-
}
1322
-
}
1323
-
1324
-
1325
-
/**
1326
-
* Is the string a number?
1327
-
*
1328
-
* @param st The string.
1329
-
* @return <code>true</code>, if the input string is a number.
1330
-
*/
1331
-
protected boolean isNumber(String st) {
1332
-
if (st.isEmpty()) {
1333
-
return false;
1334
-
}
1335
-
if (st.charAt(0) == MINUS_SIGN && st.length() == 1) {
1336
-
return false;
1337
-
}
1338
-
if (st.charAt(0) == '+' && st.length() == 1) {
1339
-
return false;
1340
-
}
1341
-
if (st.charAt(0) == DECIMAL_SEPARATOR && (st.length() == 1 || !Character
1342
-
.isDigit(st.charAt(1)))) {
1343
-
return false;
1344
-
}
1345
-
if (st.charAt(0) == 'e' || st.charAt(0) == 'E') {
1346
-
return false;
1347
-
}
1348
-
for (char ch : st.toCharArray()) {
1349
-
if (!Character.isDigit(ch) && ch != MINUS_SIGN && ch != DECIMAL_SEPARATOR && ch != 'e'
1350
-
&& ch != 'E'
1351
-
&& ch != '+') {
1352
-
return false;
1353
-
}
1354
-
}
1355
-
return true;
1356
-
}
1357
-
1358
-
1359
-
/**
1360
-
* Implementation of the <i>Shunting Yard</i> algorithm to transform an infix expression to a RPN
1361
-
* expression.
1362
-
*
1363
-
* @param expression The input expression in infx.
1364
-
* @return A RPN representation of the expression, with each token as a list member.
1365
-
*/
1366
-
@SuppressWarnings("incomplete-switch")
1367
-
private List<Token> shuntingYard(String expression) {
1368
-
List<Token> outputQueue = new ArrayList<Token>();
1369
-
Stack<Token> stack = new Stack<Token>(); // NOSONAR - Stack is needed here
1370
-
1371
-
Tokenizer tokenizer = new Tokenizer(expression);
1372
-
1373
-
Token lastFunction = null;
1374
-
Token previousToken = null;
1375
-
while (tokenizer.hasNext()) {
1376
-
Token token = tokenizer.next();
1377
-
switch (token.type) {
1378
-
case STRINGPARAM:
1379
-
stack.push(token);
1380
-
break;
1381
-
case LITERAL:
1382
-
case HEX_LITERAL:
1383
-
if (previousToken != null && (previousToken.type == TokenType.LITERAL
1384
-
|| previousToken.type == TokenType.HEX_LITERAL)) {
1385
-
throw new ExpressionException("Missing operator", token.pos);
1386
-
}
1387
-
outputQueue.add(token);
1388
-
break;
1389
-
case VARIABLE:
1390
-
outputQueue.add(token);
1391
-
break;
1392
-
case FUNCTION:
1393
-
stack.push(token);
1394
-
lastFunction = token;
1395
-
break;
1396
-
case COMMA:
1397
-
if (previousToken != null && previousToken.type == TokenType.OPERATOR) {
1398
-
throw new ExpressionException(MISSING_PARAMETERS_FOR_OPERATOR + previousToken,
1399
-
previousToken.pos);
1400
-
}
1401
-
while (!stack.isEmpty() && stack.peek().type != TokenType.OPEN_PAREN) {
1402
-
outputQueue.add(stack.pop());
1403
-
}
1404
-
if (stack.isEmpty()) {
1405
-
if (lastFunction == null) {
1406
-
throw new ExpressionException("Unexpected comma", token.pos);
1407
-
} else {
1408
-
throw new ExpressionException(
1409
-
"Parse error for function " + lastFunction, token.pos);
1410
-
}
1411
-
}
1412
-
break;
1413
-
case OPERATOR: {
1414
-
/* ExpressionException thrown only for operators which <code>unaryOperator = false</code>,
1415
-
* allowing postfix unary operators without "MISSING_PARAMETERS_FOR_OPERATOR" exception.
1416
-
*/
1417
-
if (previousToken != null && operators.containsKey(token.surface)
1418
-
&& (previousToken.type == TokenType.COMMA
1419
-
|| previousToken.type == TokenType.OPEN_PAREN)) {
1420
-
if (!operators.get(token.surface).isUnaryOperator()) {
1421
-
throw new ExpressionException(
1422
-
MISSING_PARAMETERS_FOR_OPERATOR + token, token.pos);
1423
-
}
1424
-
}
1425
-
LazyOperator o1 = operators.get(token.surface);
1426
-
if (o1 == null) {
1427
-
throw new ExpressionException("Unknown operator " + token, token.pos + 1);
1428
-
}
1429
-
1430
-
shuntOperators(outputQueue, stack, o1);
1431
-
stack.push(token);
1432
-
break;
1433
-
}
1434
-
case UNARY_OPERATOR: {
1435
-
if (previousToken != null && previousToken.type != TokenType.OPERATOR
1436
-
&& previousToken.type != TokenType.COMMA && previousToken.type != TokenType.OPEN_PAREN
1437
-
&& previousToken.type != TokenType.UNARY_OPERATOR) {
1438
-
throw new ExpressionException(
1439
-
"Invalid position for unary operator " + token, token.pos);
1440
-
}
1441
-
LazyOperator o1 = operators.get(token.surface);
1442
-
if (o1 == null) {
1443
-
throw new ExpressionException(
1444
-
"Unknown unary operator " + token.surface.substring(0, token.surface.length() - 1)
1445
-
, token.pos + 1);
1446
-
}
1447
-
1448
-
shuntOperators(outputQueue, stack, o1);
1449
-
stack.push(token);
1450
-
break;
1451
-
}
1452
-
case OPEN_PAREN:
1453
-
if (previousToken != null) {
1454
-
if (previousToken.type == TokenType.LITERAL
1455
-
|| previousToken.type == TokenType.CLOSE_PAREN
1456
-
|| previousToken.type == TokenType.VARIABLE
1457
-
|| previousToken.type == TokenType.HEX_LITERAL) {
1458
-
// Implicit multiplication, e.g. 23(a+b) or (a+b)(a-b)
1459
-
Token multiplication = new Token();
1460
-
multiplication.append("*");
1461
-
multiplication.type = TokenType.OPERATOR;
1462
-
stack.push(multiplication);
1463
-
}
1464
-
// if the ( is preceded by a valid function, then it
1465
-
// denotes the start of a parameter list
1466
-
if (previousToken.type == TokenType.FUNCTION) {
1467
-
outputQueue.add(token);
1468
-
}
1469
-
}
1470
-
stack.push(token);
1471
-
break;
1472
-
case CLOSE_PAREN:
1473
-
/* Handling "MISSING_PARAMETERS_FOR_OPERATOR" for postfix unary operators followed by closed parentheses.
1474
-
*/
1475
-
if (previousToken != null && previousToken.type == TokenType.OPERATOR && !operators
1476
-
.get(previousToken.surface).isUnaryOperator()) {
1477
-
throw new ExpressionException(MISSING_PARAMETERS_FOR_OPERATOR + previousToken,
1478
-
previousToken.pos);
1479
-
}
1480
-
while (!stack.isEmpty() && stack.peek().type != TokenType.OPEN_PAREN) {
1481
-
outputQueue.add(stack.pop());
1482
-
}
1483
-
if (stack.isEmpty()) {
1484
-
throw new ExpressionException("Mismatched parentheses");
1485
-
}
1486
-
stack.pop();
1487
-
if (!stack.isEmpty() && stack.peek().type == TokenType.FUNCTION) {
1488
-
outputQueue.add(stack.pop());
1489
-
}
1490
-
}
1491
-
previousToken = token;
1492
-
}
1493
-
1494
-
while (!stack.isEmpty()) {
1495
-
Token element = stack.pop();
1496
-
if (element.type == TokenType.OPEN_PAREN || element.type == TokenType.CLOSE_PAREN) {
1497
-
throw new ExpressionException("Mismatched parentheses");
1498
-
}
1499
-
outputQueue.add(element);
1500
-
}
1501
-
return outputQueue;
1502
-
}
1503
-
1504
-
1505
-
private void shuntOperators(List<Token> outputQueue, Stack<Token> stack,
1506
-
LazyOperator o1) { // NOSONAR - Stack is needed here
1507
-
Expression.Token nextToken = stack.isEmpty() ? null : stack.peek();
1508
-
while (nextToken != null
1509
-
&& (nextToken.type == Expression.TokenType.OPERATOR
1510
-
|| nextToken.type == Expression.TokenType.UNARY_OPERATOR)
1511
-
&& (
1512
-
(o1.isLeftAssoc() && o1.getPrecedence() <= operators.get(nextToken.surface).getPrecedence())
1513
-
|| (o1.getPrecedence() < operators.get(nextToken.surface).getPrecedence()))) {
1514
-
outputQueue.add(stack.pop());
1515
-
nextToken = stack.isEmpty() ? null : stack.peek();
1516
-
}
1517
-
}
1518
-
1519
-
1520
-
/**
1521
-
* Evaluates the expression.
1522
-
*
1523
-
* @return The result of the expression. Trailing zeros are stripped.
1524
-
*/
1525
-
public BigDecimal eval() {
1526
-
return eval(true);
1527
-
}
1528
-
1529
-
1530
-
/**
1531
-
* Evaluates the expression.
1532
-
*
1533
-
* @param stripTrailingZeros If set to <code>true</code> trailing zeros in the result are
1534
-
* stripped.
1535
-
* @return The result of the expression.
1536
-
*/
1537
-
public BigDecimal eval(boolean stripTrailingZeros) {
1538
-
1539
-
Deque<LazyNumber> stack = new ArrayDeque<LazyNumber>();
1540
-
1541
-
for (final Token token : getRPN()) {
1542
-
switch (token.type) {
1543
-
case UNARY_OPERATOR: {
1544
-
final LazyNumber value = stack.pop();
1545
-
LazyNumber result = new LazyNumber() {
1546
-
public BigDecimal eval() {
1547
-
return operators.get(token.surface).eval(value, null).eval();
1548
-
}
1549
-
1550
-
@Override
1551
-
public String getString() {
1552
-
return String.valueOf(operators.get(token.surface).eval(value, null).eval());
1553
-
}
1554
-
};
1555
-
stack.push(result);
1556
-
break;
1557
-
}
1558
-
case OPERATOR:
1559
-
/* IF-ELSE block to handle operators with 1 or 2 operands, allowing for the
1560
-
* second operand <code>v2 = null</code> in operators which <code>unaryOperator = true</code>.
1561
-
*/
1562
-
if (operators.get(token.surface).isUnaryOperator()) {
1563
-
final LazyNumber value = stack.pop();
1564
-
LazyNumber result = new LazyNumber() {
1565
-
public BigDecimal eval() {
1566
-
return operators.get(token.surface).eval(value, null).eval();
1567
-
}
1568
-
1569
-
@Override
1570
-
public String getString() {
1571
-
return String.valueOf(operators.get(token.surface).eval(value, null).eval());
1572
-
}
1573
-
};
1574
-
stack.push(result);
1575
-
break;
1576
-
} else {
1577
-
final LazyNumber v1 = stack.pop();
1578
-
final LazyNumber v2 = stack.pop();
1579
-
LazyNumber result = new LazyNumber() {
1580
-
public BigDecimal eval() {
1581
-
return operators.get(token.surface).eval(v2, v1).eval();
1582
-
}
1583
-
1584
-
public String getString() {
1585
-
return String.valueOf(operators.get(token.surface).eval(v2, v1).eval());
1586
-
}
1587
-
};
1588
-
stack.push(result);
1589
-
break;
1590
-
}
1591
-
case VARIABLE:
1592
-
if (!variables.containsKey(token.surface)) {
1593
-
throw new ExpressionException("Unknown operator or function: " + token);
1594
-
}
1595
-
1596
-
stack.push(new LazyNumber() {
1597
-
public BigDecimal eval() {
1598
-
LazyNumber lazyVariable = variables.get(token.surface);
1599
-
BigDecimal value = lazyVariable == null ? null : lazyVariable.eval();
1600
-
return value == null ? null : value.round(mc);
1601
-
}
1602
-
1603
-
public String getString() {
1604
-
LazyNumber lazyVariable = variables.get(token.surface);
1605
-
return lazyVariable.getString();
1606
-
}
1607
-
});
1608
-
break;
1609
-
case FUNCTION:
1610
-
com.udojava.evalex.LazyFunction f = functions.get(token.surface.toUpperCase(Locale.ROOT));
1611
-
ArrayList<LazyNumber> p = new ArrayList<LazyNumber>(
1612
-
!f.numParamsVaries() ? f.getNumParams() : 0);
1613
-
// pop parameters off the stack until we hit the start of
1614
-
// this function's parameter list
1615
-
while (!stack.isEmpty() && stack.peek() != PARAMS_START) {
1616
-
p.add(0, stack.pop());
1617
-
}
1618
-
1619
-
if (stack.peek() == PARAMS_START) {
1620
-
stack.pop();
1621
-
}
1622
-
1623
-
LazyNumber fResult = f.lazyEval(p);
1624
-
stack.push(fResult);
1625
-
break;
1626
-
case OPEN_PAREN:
1627
-
stack.push(PARAMS_START);
1628
-
break;
1629
-
case LITERAL:
1630
-
stack.push(new LazyNumber() {
1631
-
public BigDecimal eval() {
1632
-
if (token.surface.equalsIgnoreCase("NULL")) {
1633
-
return null;
1634
-
}
1635
-
1636
-
return new BigDecimal(token.surface, mc);
1637
-
}
1638
-
1639
-
public String getString() {
1640
-
return String.valueOf(new BigDecimal(token.surface, mc));
1641
-
}
1642
-
});
1643
-
break;
1644
-
case STRINGPARAM:
1645
-
stack.push(new LazyNumber() {
1646
-
public BigDecimal eval() {
1647
-
return null;
1648
-
}
1649
-
1650
-
public String getString() {
1651
-
return token.surface;
1652
-
}
1653
-
});
1654
-
break;
1655
-
case HEX_LITERAL:
1656
-
stack.push(new LazyNumber() {
1657
-
public BigDecimal eval() {
1658
-
return new BigDecimal(new BigInteger(token.surface.substring(2), 16), mc);
1659
-
}
1660
-
1661
-
public String getString() {
1662
-
return new BigInteger(token.surface.substring(2), 16).toString();
1663
-
}
1664
-
});
1665
-
break;
1666
-
default:
1667
-
throw new ExpressionException(
1668
-
"Unexpected token " + token.surface, token.pos);
1669
-
}
1670
-
}
1671
-
BigDecimal result = stack.pop().eval();
1672
-
if (result == null) {
1673
-
return null;
1674
-
}
1675
-
if (stripTrailingZeros) {
1676
-
result = result.stripTrailingZeros();
1677
-
}
1678
-
return result;
1679
-
}
1680
-
1681
-
1682
-
/**
1683
-
* Sets the precision for expression evaluation.
1684
-
*
1685
-
* @param precision The new precision.
1686
-
* @return The expression, allows to chain methods.
1687
-
*/
1688
-
public Expression setPrecision(int precision) {
1689
-
this.mc = new MathContext(precision);
1690
-
return this;
1691
-
}
1692
-
1693
-
1694
-
/**
1695
-
* Sets the rounding mode for expression evaluation.
1696
-
*
1697
-
* @param roundingMode The new rounding mode.
1698
-
* @return The expression, allows to chain methods.
1699
-
*/
1700
-
public Expression setRoundingMode(RoundingMode roundingMode) {
1701
-
this.mc = new MathContext(mc.getPrecision(), roundingMode);
1702
-
return this;
1703
-
}
1704
-
1705
-
1706
-
/**
1707
-
* Sets the characters other than letters and digits that are valid as the first character of a
1708
-
* variable.
1709
-
*
1710
-
* @param chars The new set of variable characters.
1711
-
* @return The expression, allows to chain methods.
1712
-
*/
1713
-
public Expression setFirstVariableCharacters(String chars) {
1714
-
this.firstVarChars = chars;
1715
-
return this;
1716
-
}
1717
-
1718
-
1719
-
/**
1720
-
* Sets the characters other than letters and digits that are valid as the second and subsequent
1721
-
* characters of a variable.
1722
-
*
1723
-
* @param chars The new set of variable characters.
1724
-
* @return The expression, allows to chain methods.
1725
-
*/
1726
-
public Expression setVariableCharacters(String chars) {
1727
-
this.varChars = chars;
1728
-
return this;
1729
-
}
1730
-
1731
-
1732
-
/**
1733
-
* Adds an operator to the list of supported operators.
1734
-
*
1735
-
* @param operator The operator to add.
1736
-
* @return The previous operator with that name, or <code>null</code> if there was none.
1737
-
*/
1738
-
@SuppressWarnings("unchecked")
1739
-
public <OPERATOR extends LazyOperator> OPERATOR addOperator(OPERATOR operator) {
1740
-
String key = operator.getOper();
1741
-
if (operator instanceof AbstractUnaryOperator) {
1742
-
key += "u";
1743
-
}
1744
-
return (OPERATOR) operators.put(key, operator);
1745
-
}
1746
-
1747
-
1748
-
/**
1749
-
* Adds a function to the list of supported functions
1750
-
*
1751
-
* @param function The function to add.
1752
-
* @return The previous operator with that name, or <code>null</code> if there was none.
1753
-
*/
1754
-
public com.udojava.evalex.Function addFunction(com.udojava.evalex.Function function) {
1755
-
return (com.udojava.evalex.Function) functions.put(function.getName(), function);
1756
-
}
1757
-
1758
-
1759
-
/**
1760
-
* Adds a lazy function function to the list of supported functions
1761
-
*
1762
-
* @param function The function to add.
1763
-
* @return The previous operator with that name, or <code>null</code> if there was none.
1764
-
*/
1765
-
public com.udojava.evalex.LazyFunction addLazyFunction(com.udojava.evalex.LazyFunction function) {
1766
-
return functions.put(function.getName(), function);
1767
-
}
1768
-
1769
-
1770
-
/**
1771
-
* Sets a variable value.
1772
-
*
1773
-
* @param variable The variable name.
1774
-
* @param value The variable value.
1775
-
* @return The expression, allows to chain methods.
1776
-
*/
1777
-
public Expression setVariable(String variable, BigDecimal value) {
1778
-
return setVariable(variable, createLazyNumber(value));
1779
-
}
1780
-
1781
-
1782
-
/**
1783
-
* Sets a variable value.
1784
-
*
1785
-
* @param variable The variable name.
1786
-
* @param value The variable value.
1787
-
* @return The expression, allows to chain methods.
1788
-
*/
1789
-
public Expression setVariable(String variable, LazyNumber value) {
1790
-
variables.put(variable, value);
1791
-
return this;
1792
-
}
1793
-
1794
-
1795
-
/**
1796
-
* Sets a variable value.
1797
-
*
1798
-
* @param variable The variable to set.
1799
-
* @param value The variable value.
1800
-
* @return The expression, allows to chain methods.
1801
-
*/
1802
-
public Expression setVariable(String variable, String value) {
1803
-
if (isNumber(value)) {
1804
-
variables.put(variable, createLazyNumber(new BigDecimal(value, mc)));
1805
-
} else if (value.equalsIgnoreCase("null")) {
1806
-
variables.put(variable, null);
1807
-
} else {
1808
-
final String expStr = value;
1809
-
variables.put(variable, new LazyNumber() {
1810
-
private final Map<String, LazyNumber> outerVariables = variables;
1811
-
private final Map<String, com.udojava.evalex.LazyFunction> outerFunctions = functions;
1812
-
private final Map<String, LazyOperator> outerOperators = operators;
1813
-
private final String innerExpressionString = expStr;
1814
-
private final MathContext inneMc = mc;
1815
-
1816
-
@Override
1817
-
public String getString() {
1818
-
return innerExpressionString;
1819
-
}
1820
-
1821
-
@Override
1822
-
public BigDecimal eval() {
1823
-
Expression innerE = new Expression(innerExpressionString, inneMc);
1824
-
innerE.variables = outerVariables;
1825
-
innerE.functions = outerFunctions;
1826
-
innerE.operators = outerOperators;
1827
-
return innerE.eval();
1828
-
}
1829
-
});
1830
-
rpn = null;
1831
-
}
1832
-
return this;
1833
-
}
1834
-
1835
-
1836
-
/**
1837
-
* Creates a new inner expression for nested expression.
1838
-
*
1839
-
* @param expression The string expression.
1840
-
* @return The inner Expression instance.
1841
-
*/
1842
-
private Expression createEmbeddedExpression(final String expression) {
1843
-
final Map<String, LazyNumber> outerVariables = variables;
1844
-
final Map<String, com.udojava.evalex.LazyFunction> outerFunctions = functions;
1845
-
final Map<String, LazyOperator> outerOperators = operators;
1846
-
final MathContext inneMc = mc;
1847
-
Expression exp = new Expression(expression, inneMc);
1848
-
exp.variables = outerVariables;
1849
-
exp.functions = outerFunctions;
1850
-
exp.operators = outerOperators;
1851
-
return exp;
1852
-
}
1853
-
1854
-
1855
-
/**
1856
-
* Sets a variable value.
1857
-
*
1858
-
* @param variable The variable to set.
1859
-
* @param value The variable value.
1860
-
* @return The expression, allows to chain methods.
1861
-
*/
1862
-
public Expression with(String variable, BigDecimal value) {
1863
-
return setVariable(variable, value);
1864
-
}
1865
-
1866
-
1867
-
/**
1868
-
* Sets a variable value.
1869
-
*
1870
-
* @param variable The variable to set.
1871
-
* @param value The variable value.
1872
-
* @return The expression, allows to chain methods.
1873
-
*/
1874
-
public Expression with(String variable, LazyNumber value) {
1875
-
return setVariable(variable, value);
1876
-
}
1877
-
1878
-
1879
-
/**
1880
-
* Sets a variable value.
1881
-
*
1882
-
* @param variable The variable to set.
1883
-
* @param value The variable value.
1884
-
* @return The expression, allows to chain methods.
1885
-
*/
1886
-
public Expression and(String variable, String value) {
1887
-
return setVariable(variable, value);
1888
-
}
1889
-
1890
-
1891
-
/**
1892
-
* Sets a variable value.
1893
-
*
1894
-
* @param variable The variable to set.
1895
-
* @param value The variable value.
1896
-
* @return The expression, allows to chain methods.
1897
-
*/
1898
-
public Expression and(String variable, BigDecimal value) {
1899
-
return setVariable(variable, value);
1900
-
}
1901
-
1902
-
1903
-
/**
1904
-
* Sets a variable value.
1905
-
*
1906
-
* @param variable The variable to set.
1907
-
* @param value The variable value.
1908
-
* @return The expression, allows to chain methods.
1909
-
*/
1910
-
public Expression and(String variable, LazyNumber value) {
1911
-
return setVariable(variable, value);
1912
-
}
1913
-
1914
-
1915
-
/**
1916
-
* Sets a variable value.
1917
-
*
1918
-
* @param variable The variable to set.
1919
-
* @param value The variable value.
1920
-
* @return The expression, allows to chain methods.
1921
-
*/
1922
-
public Expression with(String variable, String value) {
1923
-
return setVariable(variable, value);
1924
-
}
1925
-
1926
-
1927
-
/**
1928
-
* Get an iterator for this expression, allows iterating over an expression token by token.
1929
-
*
1930
-
* @return A new iterator instance for this expression.
1931
-
*/
1932
-
public Iterator<Token> getExpressionTokenizer() {
1933
-
final String expression = this.expressionString;
1934
-
1935
-
return new Tokenizer(expression);
1936
-
}
1937
-
1938
-
1939
-
/**
1940
-
* Cached access to the RPN notation of this expression, ensures only one calculation of the RPN
1941
-
* per expression instance. If no cached instance exists, a new one will be created and put to the
1942
-
* cache.
1943
-
*
1944
-
* @return The cached RPN instance.
1945
-
*/
1946
-
private List<Token> getRPN() {
1947
-
if (rpn == null) {
1948
-
rpn = shuntingYard(this.expressionString);
1949
-
validate(rpn);
1950
-
}
1951
-
return rpn;
1952
-
}
1953
-
1954
-
1955
-
/**
1956
-
* Check that the expression has enough numbers and variables to fit the requirements of the
1957
-
* operators and functions, also check for only 1 result stored at the end of the evaluation.
1958
-
*/
1959
-
private void validate(List<Token> rpn) {
1960
-
/*-
1961
-
* Thanks to Norman Ramsey:
1962
-
* http://http://stackoverflow.com/questions/789847/postfix-notation-validation
1963
-
*/
1964
-
// each push on to this stack is a new function scope, with the value of
1965
-
// each
1966
-
// layer on the stack being the count of the number of parameters in
1967
-
// that scope
1968
-
Stack<Integer> stack = new Stack<Integer>(); // NOSONAR - we need a stack here for the Stack.set() method.
1969
-
1970
-
// push the 'global' scope
1971
-
stack.push(0);
1972
-
1973
-
for (final Token token : rpn) {
1974
-
switch (token.type) {
1975
-
case UNARY_OPERATOR:
1976
-
if (stack.peek() < 1) {
1977
-
throw new ExpressionException(MISSING_PARAMETERS_FOR_OPERATOR + token);
1978
-
}
1979
-
break;
1980
-
case OPERATOR:
1981
-
LazyOperator op = operators.get(token.surface);
1982
-
/* "MISSING_PARAMETERS_FOR_OPERATOR" exception only if the stack.peek() is less
1983
-
* than the expected number of operands for this operator.
1984
-
*/
1985
-
int numberOperands = 2;
1986
-
if (op.isUnaryOperator()) {
1987
-
numberOperands = 1;
1988
-
}
1989
-
if (stack.peek() < numberOperands) {
1990
-
throw new ExpressionException(MISSING_PARAMETERS_FOR_OPERATOR + token);
1991
-
}
1992
-
/* IF condition to modify stack popping 2 parameters and adding to result
1993
-
* only for operators whose numberOperands > 1.
1994
-
*/
1995
-
if (numberOperands > 1) {
1996
-
stack.set(stack.size() - 1, stack.peek() - numberOperands + 1);
1997
-
}
1998
-
break;
1999
-
case FUNCTION:
2000
-
com.udojava.evalex.LazyFunction f = functions.get(token.surface.toUpperCase(Locale.ROOT));
2001
-
if (f == null) {
2002
-
throw new ExpressionException("Unknown function " + token, token.pos + 1);
2003
-
}
2004
-
2005
-
int numParams = stack.pop();
2006
-
if (!f.numParamsVaries() && numParams != f.getNumParams()) {
2007
-
throw new ExpressionException(
2008
-
"Function " + token + " expected " + f.getNumParams() + " parameters, got "
2009
-
+ numParams);
2010
-
}
2011
-
if (stack.isEmpty()) {
2012
-
throw new ExpressionException("Too many function calls, maximum scope exceeded");
2013
-
}
2014
-
// push the result of the function
2015
-
stack.set(stack.size() - 1, stack.peek() + 1);
2016
-
break;
2017
-
case OPEN_PAREN:
2018
-
stack.push(0);
2019
-
break;
2020
-
default:
2021
-
stack.set(stack.size() - 1, stack.peek() + 1);
2022
-
}
2023
-
}
2024
-
2025
-
if (stack.size() > 1) {
2026
-
throw new ExpressionException("Too many unhandled function parameter lists");
2027
-
} else if (stack.peek() > 1) {
2028
-
throw new ExpressionException("Too many numbers or variables");
2029
-
} else if (stack.peek() < 1) {
2030
-
throw new ExpressionException("Empty expression");
2031
-
}
2032
-
}
2033
-
2034
-
2035
-
/**
2036
-
* Get a string representation of the RPN (Reverse Polish Notation) for this expression.
2037
-
*
2038
-
* @return A string with the RPN representation for this expression.
2039
-
*/
2040
-
public String toRPN() {
2041
-
StringBuilder result = new StringBuilder();
2042
-
for (Token t : getRPN()) {
2043
-
if (result.length() != 0) {
2044
-
result.append(" ");
2045
-
}
2046
-
if (t.type == TokenType.VARIABLE && variables.containsKey(t.surface)) {
2047
-
LazyNumber innerVariable = variables.get(t.surface);
2048
-
String innerExp = innerVariable.getString();
2049
-
if (isNumber(innerExp)) { // if it is a number, then we don't
2050
-
// expan in the RPN
2051
-
result.append(t.toString());
2052
-
} else { // expand the nested variable to its RPN representation
2053
-
Expression exp = createEmbeddedExpression(innerExp);
2054
-
String nestedExpRpn = exp.toRPN();
2055
-
result.append(nestedExpRpn);
2056
-
}
2057
-
} else {
2058
-
result.append(t.toString());
2059
-
}
2060
-
}
2061
-
return result.toString();
2062
-
}
2063
-
2064
-
2065
-
/**
2066
-
* Exposing declared variables in the expression.
2067
-
*
2068
-
* @return All declared variables.
2069
-
*/
2070
-
public Set<String> getDeclaredVariables() {
2071
-
return Collections.unmodifiableSet(variables.keySet());
2072
-
}
2073
-
2074
-
2075
-
/**
2076
-
* Exposing declared operators in the expression.
2077
-
*
2078
-
* @return All declared operators.
2079
-
*/
2080
-
public Set<String> getDeclaredOperators() {
2081
-
return Collections.unmodifiableSet(operators.keySet());
2082
-
}
2083
-
2084
-
2085
-
/**
2086
-
* Exposing declared functions.
2087
-
*
2088
-
* @return All declared functions.
2089
-
*/
2090
-
public Set<String> getDeclaredFunctions() {
2091
-
return Collections.unmodifiableSet(functions.keySet());
2092
-
}
2093
-
2094
-
2095
-
/**
2096
-
* @return The original expression string
2097
-
*/
2098
-
public String getExpression() {
2099
-
return expressionString;
2100
-
}
2101
-
2102
-
2103
-
/**
2104
-
* Returns a list of the variables in the expression.
2105
-
*
2106
-
* @return A list of the variable names in this expression.
2107
-
*/
2108
-
public List<String> getUsedVariables() {
2109
-
List<String> result = new ArrayList<String>();
2110
-
Tokenizer tokenizer = new Tokenizer(expressionString);
2111
-
while (tokenizer.hasNext()) {
2112
-
Token nextToken = tokenizer.next();
2113
-
String token = nextToken.toString();
2114
-
if (nextToken.type != TokenType.VARIABLE || DEFAULT_CONSTANTS.containsKey(token)) {
2115
-
continue;
2116
-
}
2117
-
result.add(token);
2118
-
}
2119
-
return result;
2120
-
}
2121
-
2122
-
2123
-
/**
2124
-
* The original expression used to construct this expression, without variables substituted.
2125
-
*/
2126
-
public String getOriginalExpression() {
2127
-
return this.originalExpression;
2128
-
}
2129
-
2130
-
2131
-
/**
2132
-
* {@inheritDoc}
2133
-
*/
2134
-
@Override
2135
-
public boolean equals(Object o) {
2136
-
if (this == o) {
2137
-
return true;
2138
-
}
2139
-
if (o == null || getClass() != o.getClass()) {
2140
-
return false;
2141
-
}
2142
-
Expression that = (Expression) o;
2143
-
if (this.expressionString == null) {
2144
-
return that.expressionString == null;
2145
-
} else {
2146
-
return this.expressionString.equals(that.expressionString);
2147
-
}
2148
-
}
2149
-
2150
-
2151
-
/**
2152
-
* {@inheritDoc}
2153
-
*/
2154
-
@Override
2155
-
public int hashCode() {
2156
-
return this.expressionString == null ? 0 : this.expressionString.hashCode();
2157
-
}
2158
-
2159
-
2160
-
/**
2161
-
* {@inheritDoc}
2162
-
*/
2163
-
@Override
2164
-
public String toString() {
2165
-
return this.expressionString;
2166
-
}
2167
-
2168
-
2169
-
/**
2170
-
* Checks whether the expression is a boolean expression. An expression is considered a boolean
2171
-
* expression, if the last operator or function is boolean. The IF function is handled special. If
2172
-
* the third parameter is boolean, then the IF is also considered boolean, else non-boolean.
2173
-
*
2174
-
* @return <code>true</code> if the last operator/function was a boolean.
2175
-
*/
2176
-
public boolean isBoolean() {
2177
-
List<Token> rpnList = getRPN();
2178
-
if (!rpnList.isEmpty()) {
2179
-
for (int i = rpnList.size() - 1; i >= 0; i--) {
2180
-
Token t = rpnList.get(i);
2181
-
/*
2182
-
* The IF function is handled special. If the third parameter is
2183
-
* boolean, then the IF is also considered a boolean. Just skip
2184
-
* the IF function to check the second parameter.
2185
-
*/
2186
-
if (t.surface.equals("IF")) {
2187
-
continue;
2188
-
}
2189
-
if (t.type == TokenType.FUNCTION) {
2190
-
return functions.get(t.surface).isBooleanFunction();
2191
-
} else if (t.type == TokenType.OPERATOR) {
2192
-
return operators.get(t.surface).isBooleanOperator();
2193
-
}
2194
-
}
2195
-
}
2196
-
return false;
2197
-
}
2198
-
2199
-
2200
-
public List<String> infixNotation() {
2201
-
final List<String> infix = new ArrayList<String>();
2202
-
Tokenizer tokenizer = new Tokenizer(expressionString);
2203
-
while (tokenizer.hasNext()) {
2204
-
Token token = tokenizer.next();
2205
-
String infixNotation = "{" + token.type + ":" + token.surface + "}";
2206
-
infix.add(infixNotation);
2207
-
}
2208
-
return infix;
2209
-
}
2210
-
2211
-
}
-88
src/main/java/com/udojava/evalex/ExpressionSettings.java
-88
src/main/java/com/udojava/evalex/ExpressionSettings.java
···
1
-
package com.udojava.evalex;
2
-
3
-
import java.math.MathContext;
4
-
5
-
/**
6
-
* Expression settings can be used to set certain defaults, when creating a new expression. Settings
7
-
* are read only and can be created using a {@link ExpressionSettings#builder()}.
8
-
*
9
-
* @see Expression#Expression(String, ExpressionSettings)
10
-
*/
11
-
public class ExpressionSettings {
12
-
13
-
/**
14
-
* The math context to use. Default is {@link MathContext#DECIMAL32}.
15
-
*/
16
-
private MathContext mathContext;
17
-
18
-
/**
19
-
* The precedence of the power (^) operator. Default is 40.
20
-
*/
21
-
private int powerOperatorPrecedence;
22
-
23
-
private ExpressionSettings() {
24
-
// hide default constructor
25
-
}
26
-
27
-
/**
28
-
* Create a new settings object.
29
-
*
30
-
* @param mathContext The default math context to use.
31
-
* @param powerOperatorPrecedence The default power of operator precedence.
32
-
*/
33
-
public ExpressionSettings(MathContext mathContext, int powerOperatorPrecedence) {
34
-
this.mathContext = mathContext;
35
-
this.powerOperatorPrecedence = powerOperatorPrecedence;
36
-
}
37
-
38
-
/**
39
-
* Get the current math context.
40
-
*
41
-
* @return The current math context.
42
-
*/
43
-
public MathContext getMathContext() {
44
-
return mathContext;
45
-
}
46
-
47
-
/**
48
-
* Get the current power precedence.
49
-
*
50
-
* @return The current power precedence.
51
-
*/
52
-
public int getPowerOperatorPrecedence() {
53
-
return powerOperatorPrecedence;
54
-
}
55
-
56
-
public static Builder builder() {
57
-
return new Builder();
58
-
}
59
-
60
-
/**
61
-
* A builder to create a read-only {@link ExpressionSettings} instance.
62
-
*/
63
-
public static class Builder {
64
-
65
-
private MathContext mathContext = MathContext.DECIMAL32;
66
-
67
-
private int powerOperatorPrecedence = Expression.OPERATOR_PRECEDENCE_POWER;
68
-
69
-
public Builder mathContext(MathContext mathContext) {
70
-
this.mathContext = mathContext;
71
-
return this;
72
-
}
73
-
74
-
public Builder powerOperatorPrecedenceHigher() {
75
-
this.powerOperatorPrecedence = Expression.OPERATOR_PRECEDENCE_POWER_HIGHER;
76
-
return this;
77
-
}
78
-
79
-
public Builder powerOperatorPrecedence(int powerOperatorPrecedence) {
80
-
this.powerOperatorPrecedence = powerOperatorPrecedence;
81
-
return this;
82
-
}
83
-
84
-
public ExpressionSettings build() {
85
-
return new ExpressionSettings(mathContext, powerOperatorPrecedence);
86
-
}
87
-
}
88
-
}
-45
src/main/java/com/udojava/evalex/Function.java
-45
src/main/java/com/udojava/evalex/Function.java
···
1
-
/*
2
-
* Copyright 2018 Udo Klimaschewski
3
-
*
4
-
* http://UdoJava.com/
5
-
* http://about.me/udo.klimaschewski
6
-
*
7
-
* Permission is hereby granted, free of charge, to any person obtaining
8
-
* a copy of this software and associated documentation files (the
9
-
* "Software"), to deal in the Software without restriction, including
10
-
* without limitation the rights to use, copy, modify, merge, publish,
11
-
* distribute, sublicense, and/or sell copies of the Software, and to
12
-
* permit persons to whom the Software is furnished to do so, subject to
13
-
* the following conditions:
14
-
*
15
-
* The above copyright notice and this permission notice shall be
16
-
* included in all copies or substantial portions of the Software.
17
-
*
18
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
-
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
-
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
-
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
-
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
-
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
-
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
-
*
26
-
*/
27
-
package com.udojava.evalex;
28
-
29
-
import java.math.BigDecimal;
30
-
import java.util.List;
31
-
32
-
/**
33
-
* Base interface which is required for all directly evaluated functions.
34
-
*/
35
-
public interface Function extends LazyFunction {
36
-
37
-
/**
38
-
* Implementation for this function.
39
-
*
40
-
* @param parameters Parameters will be passed by the expression evaluator as a {@link List} of
41
-
* {@link BigDecimal} values.
42
-
* @return The function must return a new {@link BigDecimal} value as a computing result.
43
-
*/
44
-
BigDecimal eval(List<BigDecimal> parameters);
45
-
}
-80
src/main/java/com/udojava/evalex/LazyFunction.java
-80
src/main/java/com/udojava/evalex/LazyFunction.java
···
1
-
/*
2
-
* Copyright 2018 Udo Klimaschewski
3
-
*
4
-
* http://UdoJava.com/
5
-
* http://about.me/udo.klimaschewski
6
-
*
7
-
* Permission is hereby granted, free of charge, to any person obtaining
8
-
* a copy of this software and associated documentation files (the
9
-
* "Software"), to deal in the Software without restriction, including
10
-
* without limitation the rights to use, copy, modify, merge, publish,
11
-
* distribute, sublicense, and/or sell copies of the Software, and to
12
-
* permit persons to whom the Software is furnished to do so, subject to
13
-
* the following conditions:
14
-
*
15
-
* The above copyright notice and this permission notice shall be
16
-
* included in all copies or substantial portions of the Software.
17
-
*
18
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
-
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
-
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
-
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
-
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
-
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
-
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
-
*
26
-
*/
27
-
package com.udojava.evalex;
28
-
29
-
import com.udojava.evalex.Expression.LazyNumber;
30
-
import java.util.List;
31
-
32
-
/**
33
-
* Base interface which is required for lazily evaluated functions. A function is defined by a name,
34
-
* a number of parameters it accepts and of course the logic for evaluating the result.
35
-
*/
36
-
public interface LazyFunction {
37
-
38
-
/**
39
-
* Gets the name of this function.<br>
40
-
* <br>
41
-
* The name is use to invoke this function in the expression.
42
-
*
43
-
* @return The name of this function.
44
-
*/
45
-
String getName();
46
-
47
-
/**
48
-
* Gets the number of parameters this function accepts.<br>
49
-
* <br>
50
-
* A value of <code>-1</code> denotes that this function accepts a variable number of parameters.
51
-
*
52
-
* @return The number of parameters this function accepts.
53
-
*/
54
-
int getNumParams();
55
-
56
-
/**
57
-
* Gets whether the number of accepted parameters varies.<br>
58
-
* <br>
59
-
* That means that the function does accept an undefined amount of parameters.
60
-
*
61
-
* @return <code>true</code> if the number of accepted parameters varies.
62
-
*/
63
-
boolean numParamsVaries();
64
-
65
-
/**
66
-
* Gets whether this function evaluates to a boolean expression.
67
-
*
68
-
* @return <code>true</code> if this function evaluates to a boolean
69
-
* expression.
70
-
*/
71
-
boolean isBooleanFunction();
72
-
73
-
/**
74
-
* Lazily evaluate this function.
75
-
*
76
-
* @param lazyParams The accepted parameters.
77
-
* @return The lazy result of this function.
78
-
*/
79
-
LazyNumber lazyEval(List<LazyNumber> lazyParams);
80
-
}
-62
src/main/java/com/udojava/evalex/LazyIfNumber.java
-62
src/main/java/com/udojava/evalex/LazyIfNumber.java
···
1
-
/*
2
-
* Copyright 2018 Udo Klimaschewski
3
-
*
4
-
* http://UdoJava.com/
5
-
* http://about.me/udo.klimaschewski
6
-
*
7
-
* Permission is hereby granted, free of charge, to any person obtaining
8
-
* a copy of this software and associated documentation files (the
9
-
* "Software"), to deal in the Software without restriction, including
10
-
* without limitation the rights to use, copy, modify, merge, publish,
11
-
* distribute, sublicense, and/or sell copies of the Software, and to
12
-
* permit persons to whom the Software is furnished to do so, subject to
13
-
* the following conditions:
14
-
*
15
-
* The above copyright notice and this permission notice shall be
16
-
* included in all copies or substantial portions of the Software.
17
-
*
18
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
-
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
-
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
-
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
-
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
-
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
-
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
-
*
26
-
*/
27
-
package com.udojava.evalex;
28
-
29
-
import com.udojava.evalex.Expression.LazyNumber;
30
-
import java.math.BigDecimal;
31
-
import java.util.List;
32
-
33
-
/**
34
-
* Lazy Number for IF function created for lazily evaluated IF condition
35
-
*/
36
-
public class LazyIfNumber implements LazyNumber {
37
-
38
-
private List<LazyNumber> lazyParams;
39
-
40
-
public LazyIfNumber(List<LazyNumber> lazyParams) {
41
-
this.lazyParams = lazyParams;
42
-
}
43
-
44
-
@Override
45
-
public BigDecimal eval() {
46
-
BigDecimal result = lazyParams.get(0).eval();
47
-
assertNotNull(result);
48
-
boolean isTrue = result.compareTo(BigDecimal.ZERO) != 0;
49
-
return isTrue ? lazyParams.get(1).eval() : lazyParams.get(2).eval();
50
-
}
51
-
52
-
@Override
53
-
public String getString() {
54
-
return lazyParams.get(0).getString();
55
-
}
56
-
57
-
private void assertNotNull(BigDecimal v1) {
58
-
if (v1 == null) {
59
-
throw new ArithmeticException("Operand may not be null");
60
-
}
61
-
}
62
-
}
-88
src/main/java/com/udojava/evalex/LazyOperator.java
-88
src/main/java/com/udojava/evalex/LazyOperator.java
···
1
-
/*
2
-
* Copyright 2018 Udo Klimaschewski
3
-
*
4
-
* http://UdoJava.com/
5
-
* http://about.me/udo.klimaschewski
6
-
*
7
-
* Permission is hereby granted, free of charge, to any person obtaining
8
-
* a copy of this software and associated documentation files (the
9
-
* "Software"), to deal in the Software without restriction, including
10
-
* without limitation the rights to use, copy, modify, merge, publish,
11
-
* distribute, sublicense, and/or sell copies of the Software, and to
12
-
* permit persons to whom the Software is furnished to do so, subject to
13
-
* the following conditions:
14
-
*
15
-
* The above copyright notice and this permission notice shall be
16
-
* included in all copies or substantial portions of the Software.
17
-
*
18
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
-
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
-
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
-
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
-
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
-
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
-
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
-
*
26
-
*/
27
-
package com.udojava.evalex;
28
-
29
-
import com.udojava.evalex.Expression.LazyNumber;
30
-
31
-
/**
32
-
* Base interface which is required for all operators.
33
-
*/
34
-
public interface LazyOperator {
35
-
36
-
37
-
/**
38
-
* Gets the String that is used to denote the operator in the expression.
39
-
*
40
-
* @return The String that is used to denote the operator in the expression.
41
-
*/
42
-
String getOper();
43
-
44
-
45
-
/**
46
-
* Gets the precedence value of this operator.
47
-
*
48
-
* @return the precedence value of this operator.
49
-
*/
50
-
int getPrecedence();
51
-
52
-
53
-
/**
54
-
* Gets whether this operator is left associative (<code>true</code>) or if this operator is right
55
-
* associative (<code>false</code>).
56
-
*
57
-
* @return <code>true</code> if this operator is left associative.
58
-
*/
59
-
boolean isLeftAssoc();
60
-
61
-
62
-
/**
63
-
* Gets whether this operator evaluates to a boolean expression.
64
-
*
65
-
* @return <code>true</code> if this operator evaluates to a boolean
66
-
* expression.
67
-
*/
68
-
boolean isBooleanOperator();
69
-
70
-
71
-
/**
72
-
* Gets whether this operator is unary or not.
73
-
*
74
-
* @return <code>true</code> if this operator expects 1 operand, otherwise
75
-
* <code>false</code> if this operator expects 2 operands.
76
-
*/
77
-
boolean isUnaryOperator();
78
-
79
-
80
-
/**
81
-
* Implementation for this operator.
82
-
*
83
-
* @param v1 Operand 1.
84
-
* @param v2 Operand 2.
85
-
* @return The result of the operation.
86
-
*/
87
-
LazyNumber eval(LazyNumber v1, LazyNumber v2);
88
-
}
-46
src/main/java/com/udojava/evalex/Operator.java
-46
src/main/java/com/udojava/evalex/Operator.java
···
1
-
/*
2
-
* Copyright 2018 Udo Klimaschewski
3
-
*
4
-
* http://UdoJava.com/
5
-
* http://about.me/udo.klimaschewski
6
-
*
7
-
* Permission is hereby granted, free of charge, to any person obtaining
8
-
* a copy of this software and associated documentation files (the
9
-
* "Software"), to deal in the Software without restriction, including
10
-
* without limitation the rights to use, copy, modify, merge, publish,
11
-
* distribute, sublicense, and/or sell copies of the Software, and to
12
-
* permit persons to whom the Software is furnished to do so, subject to
13
-
* the following conditions:
14
-
*
15
-
* The above copyright notice and this permission notice shall be
16
-
* included in all copies or substantial portions of the Software.
17
-
*
18
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
-
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
-
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
-
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
-
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
-
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
-
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
-
*
26
-
*/
27
-
package com.udojava.evalex;
28
-
29
-
import java.math.BigDecimal;
30
-
31
-
/**
32
-
* Base interface which is required for all operators. Abstract class AbstractOperator accepts
33
-
* postfix unary operators with the second operand v2=null.
34
-
*/
35
-
public interface Operator extends LazyOperator {
36
-
37
-
38
-
/**
39
-
* Implementation for this operator.
40
-
*
41
-
* @param v1 Operand 1.
42
-
* @param v2 Operand 2. Null for postfix unary operators.
43
-
* @return The result of the operation.
44
-
*/
45
-
BigDecimal eval(BigDecimal v1, BigDecimal v2);
46
-
}
-7
src/main/java/com/udojava/evalex/TokenizerException.java
-7
src/main/java/com/udojava/evalex/TokenizerException.java
-3
src/main/java9/module-info.java
-3
src/main/java9/module-info.java
+50
src/test/java/com/ezylang/evalex/BaseEvaluationTest.java
+50
src/test/java/com/ezylang/evalex/BaseEvaluationTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import com.ezylang.evalex.config.ExpressionConfiguration;
21
+
import com.ezylang.evalex.config.TestConfigurationProvider;
22
+
import com.ezylang.evalex.data.EvaluationValue;
23
+
import com.ezylang.evalex.parser.ParseException;
24
+
25
+
public abstract class BaseEvaluationTest {
26
+
27
+
protected void assertExpressionHasExpectedResult(String expression, String expectedResult)
28
+
throws EvaluationException, ParseException {
29
+
assertThat(
30
+
evaluate(
31
+
expression,
32
+
TestConfigurationProvider.StandardConfigurationWithAdditionalTestOperators)
33
+
.getStringValue())
34
+
.isEqualTo(expectedResult);
35
+
}
36
+
37
+
protected void assertExpressionHasExpectedResult(
38
+
String expression, String expectedResult, ExpressionConfiguration expressionConfiguration)
39
+
throws EvaluationException, ParseException {
40
+
assertThat(evaluate(expression, expressionConfiguration).getStringValue())
41
+
.isEqualTo(expectedResult);
42
+
}
43
+
44
+
private EvaluationValue evaluate(String expressionString, ExpressionConfiguration configuration)
45
+
throws EvaluationException, ParseException {
46
+
Expression expression = new Expression(expressionString, configuration);
47
+
48
+
return expression.evaluate();
49
+
}
50
+
}
+42
src/test/java/com/ezylang/evalex/BaseExceptionTest.java
+42
src/test/java/com/ezylang/evalex/BaseExceptionTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import org.junit.jupiter.api.Test;
21
+
22
+
class BaseExceptionTest {
23
+
24
+
@Test
25
+
void testCreation() {
26
+
BaseException exception = new BaseException(2, 4, "test", "message");
27
+
28
+
assertThat(exception.getStartPosition()).isEqualTo(2);
29
+
assertThat(exception.getEndPosition()).isEqualTo(4);
30
+
assertThat(exception.getTokenString()).isEqualTo("test");
31
+
assertThat(exception.getMessage()).isEqualTo("message");
32
+
}
33
+
34
+
@Test
35
+
void testToString() {
36
+
BaseException exception = new BaseException(2, 4, "test", "message");
37
+
38
+
assertThat(exception)
39
+
.hasToString(
40
+
"BaseException(startPosition=2, endPosition=4, tokenString=test, message=message)");
41
+
}
42
+
}
+35
src/test/java/com/ezylang/evalex/BaseExpressionEvaluatorTest.java
+35
src/test/java/com/ezylang/evalex/BaseExpressionEvaluatorTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex;
17
+
18
+
import com.ezylang.evalex.config.ExpressionConfiguration;
19
+
import com.ezylang.evalex.config.TestConfigurationProvider;
20
+
import com.ezylang.evalex.parser.ParseException;
21
+
22
+
public abstract class BaseExpressionEvaluatorTest {
23
+
24
+
final ExpressionConfiguration configuration =
25
+
TestConfigurationProvider.StandardConfigurationWithAdditionalTestOperators;
26
+
27
+
String evaluate(String expressionString) throws ParseException, EvaluationException {
28
+
Expression expression = createExpression(expressionString);
29
+
return expression.evaluate().getStringValue();
30
+
}
31
+
32
+
Expression createExpression(String expressionString) {
33
+
return new Expression(expressionString, configuration);
34
+
}
35
+
}
+230
src/test/java/com/ezylang/evalex/EvalEx2CompatibilityTest.java
+230
src/test/java/com/ezylang/evalex/EvalEx2CompatibilityTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import com.ezylang.evalex.config.ExpressionConfiguration;
21
+
import com.ezylang.evalex.parser.ParseException;
22
+
import java.math.BigDecimal;
23
+
import java.math.MathContext;
24
+
import org.junit.jupiter.api.Test;
25
+
26
+
class EvalEx2CompatibilityTest {
27
+
28
+
@Test
29
+
void testContinuousUnary() throws EvaluationException, ParseException {
30
+
assertThat(evaluateToNumber("++1")).isEqualByComparingTo("1");
31
+
assertThat(evaluateToNumber("--1")).isEqualByComparingTo("1");
32
+
assertThat(evaluateToNumber("+-1")).isEqualByComparingTo("-1");
33
+
assertThat(evaluateToNumber("-+1")).isEqualByComparingTo("-1");
34
+
assertThat(evaluateToNumber("1-+1")).isEqualByComparingTo("0");
35
+
assertThat(evaluateToNumber("-+---+++--++-1")).isEqualByComparingTo("-1");
36
+
assertThat(evaluateToNumber("1--++++---2+-+----1")).isEqualByComparingTo("-2");
37
+
}
38
+
39
+
@Test
40
+
void testBrackets() throws EvaluationException, ParseException {
41
+
assertThat(evaluateToNumber("(1+2)")).isEqualByComparingTo("3");
42
+
assertThat(evaluateToNumber("((1+2))")).isEqualByComparingTo("3");
43
+
assertThat(evaluateToNumber("(((1+2)))")).isEqualByComparingTo("3");
44
+
assertThat(evaluateToNumber("(1+2)*(1+2)")).isEqualByComparingTo("9");
45
+
assertThat(evaluateToNumber("(1+2)*(1+2)+1")).isEqualByComparingTo("10");
46
+
assertThat(evaluateToNumber("(1+2)*((1+2)+1)")).isEqualByComparingTo("12");
47
+
}
48
+
49
+
@Test
50
+
void testSimple() throws EvaluationException, ParseException {
51
+
assertThat(evaluateToNumber("1+2")).isEqualByComparingTo("3");
52
+
assertThat(evaluateToNumber("4/2")).isEqualByComparingTo("2");
53
+
assertThat(evaluateToNumber("3+4/2")).isEqualByComparingTo("5");
54
+
assertThat(evaluateToNumber("(3+4)/2")).isEqualByComparingTo("3.5");
55
+
assertThat(evaluateToNumber("4.2*1.9")).isEqualByComparingTo("7.98");
56
+
assertThat(evaluateToNumber("8%3")).isEqualByComparingTo("2");
57
+
assertThat(evaluateToNumber("8%2")).isEqualByComparingTo("0");
58
+
assertThat(evaluateToNumber("2*.1")).isEqualByComparingTo("0.2");
59
+
}
60
+
61
+
@Test
62
+
void testUnaryMinus() throws EvaluationException, ParseException {
63
+
assertThat(evaluateToNumber("-3")).isEqualByComparingTo("-3");
64
+
assertThat(evaluateToNumber("-SQRT(4)")).isEqualByComparingTo("-2");
65
+
assertThat(evaluateToNumber("-(5*3+(+10-13))")).isEqualByComparingTo("-12");
66
+
assertThat(evaluateToNumber("-2+3/4*-1")).isEqualByComparingTo("-2.75");
67
+
assertThat(evaluateToNumber("-3^2")).isEqualByComparingTo("9");
68
+
assertThat(evaluateToNumber("4^-0.5")).isEqualByComparingTo("0.5");
69
+
assertThat(evaluateToNumber("-2+3/4")).isEqualByComparingTo("-1.25");
70
+
assertThat(evaluateToNumber("-(3+-4*-1/-2)")).isEqualByComparingTo("-1");
71
+
assertThat(evaluateToNumber("2+-.2")).isEqualByComparingTo("1.8");
72
+
}
73
+
74
+
@Test
75
+
void testUnaryPlus() throws EvaluationException, ParseException {
76
+
assertThat(evaluateToNumber("+3")).isEqualByComparingTo("3");
77
+
assertThat(evaluateToNumber("+(3-1+2)")).isEqualByComparingTo("4");
78
+
assertThat(evaluateToNumber("+(3-(+1)+2)")).isEqualByComparingTo("4");
79
+
assertThat(evaluateToNumber("+3^2")).isEqualByComparingTo("9");
80
+
}
81
+
82
+
@Test
83
+
void testPow() throws EvaluationException, ParseException {
84
+
assertThat(evaluateToNumber("2^4")).isEqualByComparingTo("16");
85
+
assertThat(evaluateToNumber("2^8")).isEqualByComparingTo("256");
86
+
assertThat(evaluateToNumber("3^2")).isEqualByComparingTo("9");
87
+
assertThat(evaluateToNumber("2.5^2")).isEqualByComparingTo("6.25");
88
+
assertThat(evaluateToNumber("2.6^3.5")).isEqualByComparingTo("28.34045");
89
+
}
90
+
91
+
@Test
92
+
void testSqrt() throws EvaluationException, ParseException {
93
+
assertThat(evaluateToNumber("SQRT(16)")).isEqualByComparingTo("4");
94
+
assertThat(evaluateToNumber("SQRT(2)")).isEqualByComparingTo("1.4142135");
95
+
96
+
Expression expression =
97
+
new Expression(
98
+
"SQRT(2)", ExpressionConfiguration.builder().mathContext(new MathContext(128)).build());
99
+
assertThat(expression.evaluate().getNumberValue())
100
+
.isEqualByComparingTo(
101
+
"1.41421356237309504880168872420969807856967187537694807317667973799073247846210703885038753432764157273501384623091229702492483605");
102
+
103
+
assertThat(evaluateToNumber("SQRT(5)")).isEqualByComparingTo("2.2360679");
104
+
assertThat(evaluateToNumber("SQRT(9875)")).isEqualByComparingTo("99.3730345");
105
+
assertThat(evaluateToNumber("SQRT(5.55)")).isEqualByComparingTo("2.3558437");
106
+
assertThat(evaluateToNumber("SQRT(0)")).isEqualByComparingTo("0");
107
+
}
108
+
109
+
@Test
110
+
void testTrigonometry() throws EvaluationException, ParseException { // NOSONAR - >=25 assertions
111
+
assertThat(evaluateToNumber("SIN(30)")).isEqualByComparingTo("0.5");
112
+
assertThat(evaluateToNumber("COS(30)")).isEqualByComparingTo("0.8660254");
113
+
assertThat(evaluateToNumber("TAN(30)")).isEqualByComparingTo("0.5773503");
114
+
assertThat(evaluateToNumber("SINH(30)")).isEqualByComparingTo("5343237000000");
115
+
assertThat(evaluateToNumber("COSH(30)")).isEqualByComparingTo("5343237000000");
116
+
assertThat(evaluateToNumber("TANH(30)")).isEqualByComparingTo("1");
117
+
assertThat(evaluateToNumber("RAD(30)")).isEqualByComparingTo("0.5235988");
118
+
assertThat(evaluateToNumber("DEG(30)")).isEqualByComparingTo("1718.873");
119
+
assertThat(evaluateToNumber("ATAN(0.5773503)")).isEqualByComparingTo("30");
120
+
assertThat(evaluateToNumber("ATAN2(0.5773503, 1)")).isEqualByComparingTo("30");
121
+
assertThat(evaluateToNumber("ATAN2(2, 3)")).isEqualByComparingTo("33.69007");
122
+
assertThat(evaluateToNumber("ATAN2(2, -3)")).isEqualByComparingTo("146.3099");
123
+
assertThat(evaluateToNumber("ATAN2(-2, -3)")).isEqualByComparingTo("-146.3099");
124
+
assertThat(evaluateToNumber("ATAN2(-2, 3)")).isEqualByComparingTo("-33.69007");
125
+
assertThat(evaluateToNumber("SEC(30)")).isEqualByComparingTo("1.154701");
126
+
assertThat(evaluateToNumber("SEC(45)")).isEqualByComparingTo("1.414214");
127
+
assertThat(evaluateToNumber("SEC(60)")).isEqualByComparingTo("2");
128
+
assertThat(evaluateToNumber("SEC(75)")).isEqualByComparingTo("3.863703");
129
+
assertThat(evaluateToNumber("CSC(30)")).isEqualByComparingTo("2");
130
+
assertThat(evaluateToNumber("CSC(45)")).isEqualByComparingTo("1.414214");
131
+
assertThat(evaluateToNumber("CSC(60)")).isEqualByComparingTo("1.154701");
132
+
assertThat(evaluateToNumber("CSC(75)")).isEqualByComparingTo("1.035276");
133
+
assertThat(evaluateToNumber("SECH(30)")).isEqualByComparingTo("0.0000000000001871525");
134
+
assertThat(evaluateToNumber("SECH(45)")).isEqualByComparingTo("0.00000000000000000005725037");
135
+
assertThat(evaluateToNumber("SECH(60)"))
136
+
.isEqualByComparingTo("0.00000000000000000000000001751302");
137
+
assertThat(evaluateToNumber("SECH(75)"))
138
+
.isEqualByComparingTo("0.000000000000000000000000000000005357274");
139
+
assertThat(evaluateToNumber("CSCH(30)")).isEqualByComparingTo("0.0000000000001871525");
140
+
assertThat(evaluateToNumber("CSCH(45)")).isEqualByComparingTo("0.00000000000000000005725037");
141
+
assertThat(evaluateToNumber("CSCH(60)"))
142
+
.isEqualByComparingTo("0.00000000000000000000000001751302");
143
+
assertThat(evaluateToNumber("CSCH(75)"))
144
+
.isEqualByComparingTo("0.000000000000000000000000000000005357274");
145
+
assertThat(evaluateToNumber("COT(30)")).isEqualByComparingTo("1.732051");
146
+
assertThat(evaluateToNumber("COT(45)")).isEqualByComparingTo("1");
147
+
assertThat(evaluateToNumber("COT(60)")).isEqualByComparingTo("0.5773503");
148
+
assertThat(evaluateToNumber("COT(75)")).isEqualByComparingTo("0.2679492");
149
+
assertThat(evaluateToNumber("ACOT(30)")).isEqualByComparingTo("1.909152");
150
+
assertThat(evaluateToNumber("ACOT(45)")).isEqualByComparingTo("1.27303");
151
+
assertThat(evaluateToNumber("ACOT(60)")).isEqualByComparingTo("0.9548413");
152
+
assertThat(evaluateToNumber("ACOT(75)")).isEqualByComparingTo("0.7638985");
153
+
assertThat(evaluateToNumber("COTH(30)")).isEqualByComparingTo("1");
154
+
assertThat(evaluateToNumber("COTH(1.2)")).isEqualByComparingTo("1.199538");
155
+
assertThat(evaluateToNumber("COTH(2.4)")).isEqualByComparingTo("1.016596");
156
+
assertThat(evaluateToNumber("ASINH(30)")).isEqualByComparingTo("4.094622");
157
+
assertThat(evaluateToNumber("ASINH(45)")).isEqualByComparingTo("4.499933");
158
+
assertThat(evaluateToNumber("ASINH(60)")).isEqualByComparingTo("4.787561");
159
+
assertThat(evaluateToNumber("ASINH(75)")).isEqualByComparingTo("5.01068");
160
+
assertThat(evaluateToNumber("ACOSH(1)")).isEqualByComparingTo("0");
161
+
assertThat(evaluateToNumber("ACOSH(30)")).isEqualByComparingTo("4.094067");
162
+
assertThat(evaluateToNumber("ACOSH(45)")).isEqualByComparingTo("4.499686");
163
+
assertThat(evaluateToNumber("ACOSH(60)")).isEqualByComparingTo("4.787422");
164
+
assertThat(evaluateToNumber("ACOSH(75)")).isEqualByComparingTo("5.010591");
165
+
assertThat(evaluateToNumber("ATANH(0)")).isEqualByComparingTo("0");
166
+
assertThat(evaluateToNumber("ATANH(0.5)")).isEqualByComparingTo("0.5493061");
167
+
assertThat(evaluateToNumber("ATANH(-0.5)")).isEqualByComparingTo("-0.5493061");
168
+
}
169
+
170
+
@Test
171
+
void testMinMaxAbs() throws EvaluationException, ParseException {
172
+
assertThat(evaluateToNumber("MAX(3.78787,3.78786)")).isEqualByComparingTo("3.78787");
173
+
assertThat(evaluateToNumber("max(3.78786,3.78787)")).isEqualByComparingTo("3.78787");
174
+
assertThat(evaluateToNumber("MIN(3.78787,3.78786)")).isEqualByComparingTo("3.78786");
175
+
assertThat(evaluateToNumber("Min(3.78786,3.78787)")).isEqualByComparingTo("3.78786");
176
+
assertThat(evaluateToNumber("aBs(-2.123)")).isEqualByComparingTo("2.123");
177
+
assertThat(evaluateToNumber("abs(2.123)")).isEqualByComparingTo("2.123");
178
+
}
179
+
180
+
@Test
181
+
void testRounding() throws EvaluationException, ParseException {
182
+
assertThat(evaluateToNumber("round(3.78787,1)")).isEqualByComparingTo("3.8");
183
+
assertThat(evaluateToNumber("round(3.78787,3)")).isEqualByComparingTo("3.788");
184
+
assertThat(evaluateToNumber("round(3.7345,3)")).isEqualByComparingTo("3.734");
185
+
assertThat(evaluateToNumber("round(-3.7345,3)")).isEqualByComparingTo("-3.734");
186
+
assertThat(evaluateToNumber("round(-3.78787,2)")).isEqualByComparingTo("-3.79");
187
+
assertThat(evaluateToNumber("round(123.78787,2)")).isEqualByComparingTo("123.79");
188
+
assertThat(evaluateToNumber("floor(3.78787)")).isEqualByComparingTo("3");
189
+
assertThat(evaluateToNumber("ceiling(3.78787)")).isEqualByComparingTo("4");
190
+
assertThat(evaluateToNumber("floor(-2.1)")).isEqualByComparingTo("-3");
191
+
assertThat(evaluateToNumber("ceiling(-2.1)")).isEqualByComparingTo("-2");
192
+
}
193
+
194
+
@Test
195
+
void testSciNotation() throws EvaluationException, ParseException {
196
+
// simple
197
+
assertThat(evaluateToNumber("1e10")).isEqualByComparingTo("10000000000");
198
+
assertThat(evaluateToNumber("1E10")).isEqualByComparingTo("10000000000");
199
+
assertThat(evaluateToNumber("123.456E3")).isEqualByComparingTo("123456");
200
+
assertThat(evaluateToNumber("2.5e0")).isEqualByComparingTo("2.5");
201
+
202
+
// negative
203
+
assertThat(evaluateToNumber("1e-10")).isEqualByComparingTo("0.0000000001");
204
+
assertThat(evaluateToNumber("1E-10")).isEqualByComparingTo("0.0000000001");
205
+
assertThat(evaluateToNumber("2135E-4")).isEqualByComparingTo("0.2135");
206
+
207
+
// positive
208
+
assertThat(evaluateToNumber("1e+10")).isEqualByComparingTo("10000000000");
209
+
assertThat(evaluateToNumber("1E+10")).isEqualByComparingTo("10000000000");
210
+
211
+
// combined
212
+
assertThat(evaluateToNumber("sqrt(152.399025e6)")).isEqualByComparingTo("12344.9989874");
213
+
assertThat(evaluateToNumber("sin(3.e1)")).isEqualByComparingTo("0.5");
214
+
assertThat(evaluateToNumber("sin( 3.e1)")).isEqualByComparingTo("0.5");
215
+
assertThat(evaluateToNumber("sin(3.e1 )")).isEqualByComparingTo("0.5");
216
+
assertThat(evaluateToNumber("sin( 3.e1 )")).isEqualByComparingTo("0.5");
217
+
assertThat(evaluateToNumber("2.2e-16 * 10.2")).isEqualByComparingTo("2.244E-15");
218
+
}
219
+
220
+
private BigDecimal evaluateToNumber(String expression)
221
+
throws EvaluationException, ParseException {
222
+
223
+
// Given an expression with EvalEx2 compatible math context
224
+
return new Expression(
225
+
expression,
226
+
ExpressionConfiguration.builder().mathContext(MathContext.DECIMAL32).build())
227
+
.evaluate()
228
+
.getNumberValue();
229
+
}
230
+
}
+40
src/test/java/com/ezylang/evalex/ExpressionEvaluationExceptionsTest.java
+40
src/test/java/com/ezylang/evalex/ExpressionEvaluationExceptionsTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
19
+
20
+
import com.ezylang.evalex.parser.ASTNode;
21
+
import com.ezylang.evalex.parser.Token;
22
+
import com.ezylang.evalex.parser.Token.TokenType;
23
+
import org.junit.jupiter.api.Test;
24
+
25
+
class ExpressionEvaluationExceptionsTest {
26
+
27
+
@Test
28
+
void testUnexpectedToken() {
29
+
Expression expression = new Expression("1");
30
+
31
+
assertThatThrownBy(
32
+
() -> {
33
+
ASTNode node = new ASTNode(new Token(1, "(", TokenType.BRACE_OPEN));
34
+
expression.evaluateSubtree(node);
35
+
})
36
+
.isInstanceOf(EvaluationException.class)
37
+
.hasMessage(
38
+
"Unexpected evaluation token: Token(startPosition=1, value=(, type=BRACE_OPEN)");
39
+
}
40
+
}
+130
src/test/java/com/ezylang/evalex/ExpressionEvaluatorArrayTest.java
+130
src/test/java/com/ezylang/evalex/ExpressionEvaluatorArrayTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
20
+
21
+
import com.ezylang.evalex.parser.ParseException;
22
+
import java.math.BigDecimal;
23
+
import java.util.Arrays;
24
+
import java.util.List;
25
+
import org.junit.jupiter.api.Test;
26
+
27
+
class ExpressionEvaluatorArrayTest extends BaseExpressionEvaluatorTest {
28
+
29
+
@Test
30
+
void testSimpleArray() throws ParseException, EvaluationException {
31
+
List<BigDecimal> array = List.of(new BigDecimal(99));
32
+
Expression expression = createExpression("a[0]").with("a", array);
33
+
34
+
assertThat(expression.evaluate().getStringValue()).isEqualTo("99");
35
+
}
36
+
37
+
@Test
38
+
void testMultipleEntriesArray() throws ParseException, EvaluationException {
39
+
List<BigDecimal> array = Arrays.asList(new BigDecimal(2), new BigDecimal(4), new BigDecimal(6));
40
+
Expression expression = createExpression("a[0]+a[1]+a[2]").with("a", array);
41
+
42
+
assertThat(expression.evaluate().getStringValue()).isEqualTo("12");
43
+
}
44
+
45
+
@Test
46
+
void testExpressionArray() throws ParseException, EvaluationException {
47
+
List<BigDecimal> array = List.of(new BigDecimal(3));
48
+
Expression expression = createExpression("a[4-x]").with("a", array).and("x", new BigDecimal(4));
49
+
50
+
assertThat(expression.evaluate().getStringValue()).isEqualTo("3");
51
+
}
52
+
53
+
@Test
54
+
void testNestedArray() throws ParseException, EvaluationException {
55
+
List<BigDecimal> arrayA = List.of(new BigDecimal(3));
56
+
List<BigDecimal> arrayB =
57
+
Arrays.asList(new BigDecimal(2), new BigDecimal(4), new BigDecimal(6));
58
+
Expression expression =
59
+
createExpression("a[b[6-4]-x]")
60
+
.with("a", arrayA)
61
+
.and("b", arrayB)
62
+
.and("x", new BigDecimal(6));
63
+
64
+
assertThat(expression.evaluate().getStringValue()).isEqualTo("3");
65
+
}
66
+
67
+
@Test
68
+
void testStringArray() throws ParseException, EvaluationException {
69
+
List<String> array = Arrays.asList("Hello", "beautiful", "world");
70
+
Expression expression = createExpression("a[0] + \" \" + a[1] + \" \" + a[2]").with("a", array);
71
+
72
+
assertThat(expression.evaluate().getStringValue()).isEqualTo("Hello beautiful world");
73
+
}
74
+
75
+
@Test
76
+
void testBooleanArray() throws ParseException, EvaluationException {
77
+
List<Boolean> array = Arrays.asList(true, true, false);
78
+
Expression expression = createExpression("a[0] + \" \" + a[1] + \" \" + a[2]").with("a", array);
79
+
80
+
assertThat(expression.evaluate().getStringValue()).isEqualTo("true true false");
81
+
}
82
+
83
+
@Test
84
+
void testArrayOfArray() throws EvaluationException, ParseException {
85
+
List<BigDecimal> subArray1 = Arrays.asList(new BigDecimal(1), new BigDecimal(2));
86
+
List<BigDecimal> subArray2 = Arrays.asList(new BigDecimal(4), new BigDecimal(8));
87
+
88
+
List<List<BigDecimal>> array = Arrays.asList(subArray1, subArray2);
89
+
90
+
Expression expression1 = createExpression("a[0][0]").with("a", array);
91
+
Expression expression2 = createExpression("a[0][1]").with("a", array);
92
+
Expression expression3 = createExpression("a[1][0]").with("a", array);
93
+
Expression expression4 = createExpression("a[1][1]").with("a", array);
94
+
95
+
assertThat(expression1.evaluate().getStringValue()).isEqualTo("1");
96
+
assertThat(expression2.evaluate().getStringValue()).isEqualTo("2");
97
+
assertThat(expression3.evaluate().getStringValue()).isEqualTo("4");
98
+
assertThat(expression4.evaluate().getStringValue()).isEqualTo("8");
99
+
}
100
+
101
+
@Test
102
+
void testMixedArray() throws ParseException, EvaluationException {
103
+
List<?> array = Arrays.asList("Hello", new BigDecimal(4), true);
104
+
Expression expression1 = createExpression("a[0]").with("a", array);
105
+
Expression expression2 = createExpression("a[1]").with("a", array);
106
+
Expression expression3 = createExpression("a[2]").with("a", array);
107
+
108
+
assertThat(expression1.evaluate().getStringValue()).isEqualTo("Hello");
109
+
assertThat(expression2.evaluate().getStringValue()).isEqualTo("4");
110
+
assertThat(expression3.evaluate().getStringValue()).isEqualTo("true");
111
+
}
112
+
113
+
@Test
114
+
void testThrowsUnsupportedDataTypeForArray() {
115
+
assertThatThrownBy(() -> createExpression("a[0]").with("a", "aString").evaluate())
116
+
.isInstanceOf(EvaluationException.class)
117
+
.hasMessage("Unsupported data types in operation");
118
+
}
119
+
120
+
@Test
121
+
void testThrowsUnsupportedDataTypeForIndex() {
122
+
assertThatThrownBy(
123
+
() -> {
124
+
List<?> array = List.of("Hello");
125
+
createExpression("a[b]").with("a", array).and("b", "anotherString").evaluate();
126
+
})
127
+
.isInstanceOf(EvaluationException.class)
128
+
.hasMessage("Unsupported data types in operation");
129
+
}
130
+
}
+47
src/test/java/com/ezylang/evalex/ExpressionEvaluatorCombinedTest.java
+47
src/test/java/com/ezylang/evalex/ExpressionEvaluatorCombinedTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import com.ezylang.evalex.parser.ParseException;
21
+
import java.math.BigDecimal;
22
+
import java.util.HashMap;
23
+
import java.util.List;
24
+
import java.util.Map;
25
+
import org.junit.jupiter.api.Test;
26
+
27
+
class ExpressionEvaluatorCombinedTest extends BaseExpressionEvaluatorTest {
28
+
29
+
@Test
30
+
void testOrderPositionExample() throws ParseException, EvaluationException {
31
+
Map<String, Object> order = new HashMap<>();
32
+
order.put("id", 12345);
33
+
order.put("name", "Mary");
34
+
Map<String, Object> position = new HashMap<>();
35
+
position.put("article", 3114);
36
+
position.put("amount", 3);
37
+
position.put("price", new BigDecimal("14.95"));
38
+
order.put("positions", List.of(position));
39
+
40
+
Expression expression =
41
+
new Expression("order.positions[x].amount * order.positions[x].price")
42
+
.with("order", order)
43
+
.and("x", 0);
44
+
45
+
assertThat(expression.evaluate().getStringValue()).isEqualTo("44.85");
46
+
}
47
+
}
+65
src/test/java/com/ezylang/evalex/ExpressionEvaluatorConstantsTest.java
+65
src/test/java/com/ezylang/evalex/ExpressionEvaluatorConstantsTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import com.ezylang.evalex.config.ExpressionConfiguration;
21
+
import com.ezylang.evalex.data.EvaluationValue;
22
+
import com.ezylang.evalex.parser.ParseException;
23
+
import java.math.BigDecimal;
24
+
import java.util.HashMap;
25
+
import java.util.Map;
26
+
import org.junit.jupiter.api.Test;
27
+
import org.junit.jupiter.params.ParameterizedTest;
28
+
import org.junit.jupiter.params.provider.CsvSource;
29
+
30
+
class ExpressionEvaluatorConstantsTest extends BaseExpressionEvaluatorTest {
31
+
32
+
@ParameterizedTest
33
+
@CsvSource(
34
+
delimiter = ':',
35
+
value = {
36
+
"TRUE : true",
37
+
"true : true",
38
+
"False : false",
39
+
"PI : "
40
+
+ " 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679",
41
+
"e : 2.71828182845904523536028747135266249775724709369995957496696762772407663",
42
+
})
43
+
void testDefaultConstants(String expression, String expectedResult)
44
+
throws ParseException, EvaluationException {
45
+
assertThat(evaluate(expression)).isEqualTo(expectedResult);
46
+
}
47
+
48
+
@Test
49
+
void testCustomConstants() throws EvaluationException, ParseException {
50
+
Map<String, EvaluationValue> constants =
51
+
new HashMap<>() {
52
+
{
53
+
put("A", new EvaluationValue(new BigDecimal("2.5")));
54
+
put("B", new EvaluationValue(new BigDecimal("3.9")));
55
+
}
56
+
};
57
+
58
+
ExpressionConfiguration configuration =
59
+
ExpressionConfiguration.builder().defaultConstants(constants).build();
60
+
61
+
Expression expression = new Expression("a+b", configuration);
62
+
63
+
assertThat(expression.evaluate().getStringValue()).isEqualTo("6.4");
64
+
}
65
+
}
+166
src/test/java/com/ezylang/evalex/ExpressionEvaluatorDecimalPlacesTest.java
+166
src/test/java/com/ezylang/evalex/ExpressionEvaluatorDecimalPlacesTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import com.ezylang.evalex.config.ExpressionConfiguration;
21
+
import com.ezylang.evalex.parser.ParseException;
22
+
import java.math.BigDecimal;
23
+
import java.util.HashMap;
24
+
import java.util.List;
25
+
import java.util.Map;
26
+
import org.junit.jupiter.api.Test;
27
+
28
+
class ExpressionEvaluatorDecimalPlacesTest extends BaseExpressionEvaluatorTest {
29
+
30
+
@Test
31
+
void testDefaultNoRoundingLiteral() throws ParseException, EvaluationException {
32
+
assertThat(evaluate("2.12345")).isEqualTo("2.12345");
33
+
}
34
+
35
+
@Test
36
+
void testDefaultNoRoundingVariable() throws ParseException, EvaluationException {
37
+
Expression expression = new Expression("a").with("a", new BigDecimal("2.12345"));
38
+
39
+
assertThat(expression.evaluate().getStringValue()).isEqualTo("2.12345");
40
+
}
41
+
42
+
@Test
43
+
void testDefaultNoRoundingInfixOperator() throws ParseException, EvaluationException {
44
+
assertThat(evaluate("2.12345+1.54321")).isEqualTo("3.66666");
45
+
}
46
+
47
+
@Test
48
+
void testDefaultNoRoundingPrefixOperator() throws ParseException, EvaluationException {
49
+
assertThat(evaluate("-2.12345")).isEqualTo("-2.12345");
50
+
}
51
+
52
+
@Test
53
+
void testDefaultNoRoundingFunction() throws ParseException, EvaluationException {
54
+
assertThat(evaluate("SUM(2.12345,1.54321)")).isEqualTo("3.66666");
55
+
}
56
+
57
+
@Test
58
+
void testDefaultNoRoundingArray() throws ParseException, EvaluationException {
59
+
List<BigDecimal> array = List.of(new BigDecimal("1.12345"));
60
+
Expression expression = createExpression("a[0]").with("a", array);
61
+
62
+
assertThat(expression.evaluate().getStringValue()).isEqualTo("1.12345");
63
+
}
64
+
65
+
@Test
66
+
void testDefaultNoRoundingStructure() throws ParseException, EvaluationException {
67
+
Map<String, BigDecimal> structure =
68
+
new HashMap<>() {
69
+
{
70
+
put("b", new BigDecimal("1.12345"));
71
+
}
72
+
};
73
+
74
+
Expression expression = createExpression("a.b").with("a", structure);
75
+
76
+
assertThat(expression.evaluate().getStringValue()).isEqualTo("1.12345");
77
+
}
78
+
79
+
@Test
80
+
void testCustomRoundingDecimalsLiteral() throws ParseException, EvaluationException {
81
+
ExpressionConfiguration config =
82
+
ExpressionConfiguration.builder().decimalPlacesRounding(2).build();
83
+
Expression expression = new Expression("2.12345", config);
84
+
85
+
assertThat(expression.evaluate().getStringValue()).isEqualTo("2.12");
86
+
}
87
+
88
+
@Test
89
+
void testCustomRoundingDecimalsVariable() throws ParseException, EvaluationException {
90
+
ExpressionConfiguration config =
91
+
ExpressionConfiguration.builder().decimalPlacesRounding(2).build();
92
+
Expression expression = new Expression("a", config).with("a", new BigDecimal("2.126"));
93
+
94
+
assertThat(expression.evaluate().getStringValue()).isEqualTo("2.13");
95
+
}
96
+
97
+
@Test
98
+
void testCustomRoundingDecimalsInfixOperator() throws ParseException, EvaluationException {
99
+
ExpressionConfiguration config =
100
+
ExpressionConfiguration.builder().decimalPlacesRounding(3).build();
101
+
Expression expression = new Expression("2.12345+1.54321", config);
102
+
103
+
// literals are rounded first, the added and rounded again.
104
+
assertThat(expression.evaluate().getStringValue()).isEqualTo("3.666");
105
+
}
106
+
107
+
@Test
108
+
void testCustomRoundingDecimalsPrefixOperator() throws ParseException, EvaluationException {
109
+
ExpressionConfiguration config =
110
+
ExpressionConfiguration.builder().decimalPlacesRounding(3).build();
111
+
Expression expression = new Expression("-2.12345", config);
112
+
113
+
assertThat(expression.evaluate().getStringValue()).isEqualTo("-2.123");
114
+
}
115
+
116
+
@Test
117
+
void testCustomRoundingDecimalsFunction() throws ParseException, EvaluationException {
118
+
ExpressionConfiguration config =
119
+
ExpressionConfiguration.builder().decimalPlacesRounding(3).build();
120
+
Expression expression = new Expression("SUM(2.12345,1.54321)", config);
121
+
122
+
// literals are rounded first, the added and rounded again.
123
+
assertThat(expression.evaluate().getStringValue()).isEqualTo("3.666");
124
+
}
125
+
126
+
@Test
127
+
void testCustomRoundingDecimalsArray() throws ParseException, EvaluationException {
128
+
ExpressionConfiguration config =
129
+
ExpressionConfiguration.builder().decimalPlacesRounding(3).build();
130
+
List<BigDecimal> array = List.of(new BigDecimal("1.12345"));
131
+
Expression expression = new Expression("a[0]", config).with("a", array);
132
+
133
+
assertThat(expression.evaluate().getStringValue()).isEqualTo("1.123");
134
+
}
135
+
136
+
@Test
137
+
void testCustomRoundingStructure() throws ParseException, EvaluationException {
138
+
ExpressionConfiguration config =
139
+
ExpressionConfiguration.builder().decimalPlacesRounding(3).build();
140
+
Map<String, BigDecimal> structure =
141
+
new HashMap<>() {
142
+
{
143
+
put("b", new BigDecimal("1.12345"));
144
+
}
145
+
};
146
+
147
+
Expression expression = new Expression("a.b", config).with("a", structure);
148
+
149
+
assertThat(expression.evaluate().getStringValue()).isEqualTo("1.123");
150
+
}
151
+
152
+
@Test
153
+
void testDefaultStripZeros() throws EvaluationException, ParseException {
154
+
Expression expression = new Expression("9.000");
155
+
assertThat(expression.evaluate().getNumberValue()).isEqualTo("9");
156
+
}
157
+
158
+
@Test
159
+
void testDoNotStripZeros() throws EvaluationException, ParseException {
160
+
ExpressionConfiguration config =
161
+
ExpressionConfiguration.builder().stripTrailingZeros(false).build();
162
+
163
+
Expression expression = new Expression("9.000", config);
164
+
assertThat(expression.evaluate().getNumberValue()).isEqualTo("9.000");
165
+
}
166
+
}
+50
src/test/java/com/ezylang/evalex/ExpressionEvaluatorFunctionTest.java
+50
src/test/java/com/ezylang/evalex/ExpressionEvaluatorFunctionTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import com.ezylang.evalex.parser.ParseException;
21
+
import org.junit.jupiter.api.Test;
22
+
import org.junit.jupiter.params.ParameterizedTest;
23
+
import org.junit.jupiter.params.provider.CsvSource;
24
+
25
+
class ExpressionEvaluatorFunctionTest extends BaseExpressionEvaluatorTest {
26
+
27
+
@Test
28
+
void testSingleParameterFunction() throws ParseException, EvaluationException {
29
+
assertThat(evaluate("FACT(3)")).isEqualTo("6");
30
+
}
31
+
32
+
@Test
33
+
void testDualParameterFunction() throws ParseException, EvaluationException {
34
+
assertThat(evaluate("ROUND(3.123, 2)")).isEqualTo("3.12");
35
+
}
36
+
37
+
@Test
38
+
void testNestedFunctions() throws ParseException, EvaluationException {
39
+
assertThat(evaluate("FACT(ROUND(3.95, 0))")).isEqualTo("24");
40
+
}
41
+
42
+
@ParameterizedTest
43
+
@CsvSource(
44
+
delimiter = ':',
45
+
value = {"MAX(5) : 5", "MAX(4,6,3,8) : 8"})
46
+
void testVarArgFunction(String expressionString, String expectedResult)
47
+
throws ParseException, EvaluationException {
48
+
assertThat(evaluate(expressionString)).isEqualTo(expectedResult);
49
+
}
50
+
}
+41
src/test/java/com/ezylang/evalex/ExpressionEvaluatorHexadecimalTest.java
+41
src/test/java/com/ezylang/evalex/ExpressionEvaluatorHexadecimalTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import com.ezylang.evalex.parser.ParseException;
21
+
import org.junit.jupiter.params.ParameterizedTest;
22
+
import org.junit.jupiter.params.provider.CsvSource;
23
+
24
+
class ExpressionEvaluatorHexadecimalTest extends BaseExpressionEvaluatorTest {
25
+
26
+
@ParameterizedTest
27
+
@CsvSource(
28
+
delimiter = ':',
29
+
value = {
30
+
"0x0 : 0 ",
31
+
"0x1 : 1",
32
+
"0xa : 10",
33
+
"0x10 + 0xaa : 186",
34
+
"0xff + 0xff : 510",
35
+
"0X87FAB987 + 0x123 : 2281355946"
36
+
})
37
+
void testHexadecimalLiteralsEvaluation(String expression, String expectedResult)
38
+
throws ParseException, EvaluationException {
39
+
assertThat(evaluate(expression)).isEqualTo(expectedResult);
40
+
}
41
+
}
+43
src/test/java/com/ezylang/evalex/ExpressionEvaluatorPowerOfTest.java
+43
src/test/java/com/ezylang/evalex/ExpressionEvaluatorPowerOfTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import com.ezylang.evalex.config.ExpressionConfiguration;
21
+
import com.ezylang.evalex.operators.OperatorIfc;
22
+
import com.ezylang.evalex.parser.ParseException;
23
+
import org.junit.jupiter.api.Test;
24
+
25
+
class ExpressionEvaluatorPowerOfTest extends BaseExpressionEvaluatorTest {
26
+
27
+
@Test
28
+
void testPrecedenceDefault() throws ParseException, EvaluationException {
29
+
assertThat(evaluate("-2^2")).isEqualTo("4");
30
+
}
31
+
32
+
@Test
33
+
void testPrecedenceHigher() throws ParseException, EvaluationException {
34
+
ExpressionConfiguration config =
35
+
ExpressionConfiguration.builder()
36
+
.powerOfPrecedence(OperatorIfc.OPERATOR_PRECEDENCE_POWER_HIGHER)
37
+
.build();
38
+
39
+
Expression expression = new Expression("-2^2", config);
40
+
41
+
assertThat(expression.evaluate().getStringValue()).isEqualTo("-4");
42
+
}
43
+
}
+46
src/test/java/com/ezylang/evalex/ExpressionEvaluatorScientificTest.java
+46
src/test/java/com/ezylang/evalex/ExpressionEvaluatorScientificTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import com.ezylang.evalex.parser.ParseException;
21
+
import org.junit.jupiter.params.ParameterizedTest;
22
+
import org.junit.jupiter.params.provider.CsvSource;
23
+
24
+
class ExpressionEvaluatorScientificTest extends BaseExpressionEvaluatorTest {
25
+
26
+
@ParameterizedTest
27
+
@CsvSource(
28
+
delimiter = ':',
29
+
value = {
30
+
"2e3 : 2000 ",
31
+
"2e10 : 20000000000 ",
32
+
"2E3 : 2000",
33
+
"0.5e2 : 50",
34
+
"0.35E4 + 0.5e1 : 3505",
35
+
"1e-10 : 0.0000000001",
36
+
"1E-10 : 0.0000000001",
37
+
"2135E-4 : 0.2135",
38
+
"1e+10 : 10000000000",
39
+
"1E+10 : 10000000000",
40
+
"2135E+4 : 21350000"
41
+
})
42
+
void testScientificLiteralsEvaluation(String expression, String expectedResult)
43
+
throws ParseException, EvaluationException {
44
+
assertThat(evaluate(expression)).isEqualTo(expectedResult);
45
+
}
46
+
}
+100
src/test/java/com/ezylang/evalex/ExpressionEvaluatorSimpleTest.java
+100
src/test/java/com/ezylang/evalex/ExpressionEvaluatorSimpleTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import com.ezylang.evalex.parser.ParseException;
21
+
import org.junit.jupiter.params.ParameterizedTest;
22
+
import org.junit.jupiter.params.provider.CsvSource;
23
+
24
+
class ExpressionEvaluatorSimpleTest extends BaseExpressionEvaluatorTest {
25
+
26
+
@ParameterizedTest
27
+
@CsvSource(
28
+
delimiter = ':',
29
+
value = {
30
+
"1 : 1",
31
+
"1+1 : 2",
32
+
"1-1 : 0",
33
+
"1-2 : -1",
34
+
"5-3-1 : 1",
35
+
"-1+1 : 0",
36
+
"1+ -1 : 0",
37
+
"-1 + -1 : -2",
38
+
"1+2+3 : 6",
39
+
"8-4+1-2+4-2 : 5"
40
+
})
41
+
void testSimpleAdditiveEvaluation(String expression, String expectedResult)
42
+
throws ParseException, EvaluationException {
43
+
assertThat(evaluate(expression)).isEqualTo(expectedResult);
44
+
}
45
+
46
+
@ParameterizedTest
47
+
@CsvSource(
48
+
delimiter = ':',
49
+
value = {
50
+
"2/2 : 1",
51
+
"4/2 : 2",
52
+
"2*2 : 4",
53
+
"2*4 : 8",
54
+
"6/3*2 : 4",
55
+
"6*2/3 : 4",
56
+
"-1 * -1 : 1",
57
+
"-2 * 2 : -4",
58
+
"4*2/4 : 2",
59
+
"4*2*3/2/2 : 6"
60
+
})
61
+
void testSimpleMultiplicativeEvaluation(String expression, String expectedResult)
62
+
throws ParseException, EvaluationException {
63
+
assertThat(evaluate(expression)).isEqualTo(expectedResult);
64
+
}
65
+
66
+
@ParameterizedTest
67
+
@CsvSource(
68
+
delimiter = ':',
69
+
value = {
70
+
"2+3*2 : 8",
71
+
"2+3*2+2 : 10",
72
+
"2+6/3+1 : 5",
73
+
"2+6/3-1 : 3",
74
+
"1+2+3+4/2+4-2 : 10",
75
+
"2*2*2+4/2 : 10",
76
+
"1*2-2*2/2+8 : 8"
77
+
})
78
+
void testSimpleMixedPrecedence(String expression, String expectedResult)
79
+
throws ParseException, EvaluationException {
80
+
assertThat(evaluate(expression)).isEqualTo(expectedResult);
81
+
}
82
+
83
+
@ParameterizedTest
84
+
@CsvSource(
85
+
delimiter = ':',
86
+
value = {
87
+
"(3) : 3",
88
+
"(2+3)*2 : 10",
89
+
"(2+3)*(2+2) : 20",
90
+
"(2+6)/2+1 : 5",
91
+
"(2+6)/(3-1) : 4",
92
+
"(((1+2)+3)+4)/(2+4-4) : 5",
93
+
"2*2*((2+4)/2) : 12",
94
+
"1*(2-2*2)/2+8 : 7"
95
+
})
96
+
void testSimpleBraces(String expression, String expectedResult)
97
+
throws ParseException, EvaluationException {
98
+
assertThat(evaluate(expression)).isEqualTo(expectedResult);
99
+
}
100
+
}
+77
src/test/java/com/ezylang/evalex/ExpressionEvaluatorSimpleVariablesTest.java
+77
src/test/java/com/ezylang/evalex/ExpressionEvaluatorSimpleVariablesTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import com.ezylang.evalex.data.EvaluationValue;
21
+
import com.ezylang.evalex.parser.ParseException;
22
+
import java.math.BigDecimal;
23
+
import org.junit.jupiter.api.Test;
24
+
25
+
class ExpressionEvaluatorSimpleVariablesTest extends BaseExpressionEvaluatorTest {
26
+
27
+
@Test
28
+
void testSingleStringVariable() throws ParseException, EvaluationException {
29
+
Expression expression = createExpression("a").with("a", "hello");
30
+
EvaluationValue result = expression.evaluate();
31
+
assertThat(result.isStringValue()).isTrue();
32
+
assertThat(result.getStringValue()).isEqualTo("hello");
33
+
}
34
+
35
+
@Test
36
+
void testSingleNumberVariable() throws ParseException, EvaluationException {
37
+
Expression expression = createExpression("a").with("a", BigDecimal.valueOf(9));
38
+
EvaluationValue result = expression.evaluate();
39
+
assertThat(result.isNumberValue()).isTrue();
40
+
assertThat(result.getNumberValue()).isEqualTo(BigDecimal.valueOf(9));
41
+
}
42
+
43
+
@Test
44
+
void testNumbers() throws ParseException, EvaluationException {
45
+
Expression expression =
46
+
createExpression("(a+b)*(a-b)")
47
+
.with("a", BigDecimal.valueOf(9))
48
+
.and("b", BigDecimal.valueOf(5));
49
+
EvaluationValue result = expression.evaluate();
50
+
assertThat(result.isNumberValue()).isTrue();
51
+
assertThat(result.getNumberValue()).isEqualTo(BigDecimal.valueOf(56));
52
+
}
53
+
54
+
@Test
55
+
void testStrings() throws ParseException, EvaluationException {
56
+
Expression expression =
57
+
createExpression("prefix+infix+postfix")
58
+
.with("prefix", "Hello")
59
+
.and("infix", " ")
60
+
.and("postfix", "world");
61
+
EvaluationValue result = expression.evaluate();
62
+
assertThat(result.isStringValue()).isTrue();
63
+
assertThat(result.getStringValue()).isEqualTo("Hello world");
64
+
}
65
+
66
+
@Test
67
+
void testStringNumberCombined() throws ParseException, EvaluationException {
68
+
Expression expression =
69
+
createExpression("prefix+infix+postfix")
70
+
.with("prefix", "Hello")
71
+
.and("infix", BigDecimal.valueOf(2))
72
+
.and("postfix", "world");
73
+
EvaluationValue result = expression.evaluate();
74
+
assertThat(result.isStringValue()).isTrue();
75
+
assertThat(result.getStringValue()).isEqualTo("Hello2world");
76
+
}
77
+
}
+64
src/test/java/com/ezylang/evalex/ExpressionEvaluatorStringTest.java
+64
src/test/java/com/ezylang/evalex/ExpressionEvaluatorStringTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import com.ezylang.evalex.parser.ParseException;
21
+
import org.junit.jupiter.api.Test;
22
+
23
+
class ExpressionEvaluatorStringTest extends BaseExpressionEvaluatorTest {
24
+
25
+
@Test
26
+
void testStringConcatenation() throws ParseException, EvaluationException {
27
+
assertThat(evaluate("\"Hello\"+\" world\"")).isEqualTo("Hello world");
28
+
}
29
+
30
+
@Test
31
+
void testStringAndNumberConcatenation() throws ParseException, EvaluationException {
32
+
assertThat(evaluate("\"Test\"+1")).isEqualTo("Test1");
33
+
}
34
+
35
+
@Test
36
+
void testStringAndDoubleNumberConcatenation() throws ParseException, EvaluationException {
37
+
assertThat(evaluate("\"Test\"+1+2")).isEqualTo("Test12");
38
+
}
39
+
40
+
@Test
41
+
void testNumberAndStringConcatenation() throws ParseException, EvaluationException {
42
+
assertThat(evaluate("1+\"Test\"")).isEqualTo("1Test");
43
+
}
44
+
45
+
@Test
46
+
void testDoubleNumberAndStringConcatenation() throws ParseException, EvaluationException {
47
+
assertThat(evaluate("1+2+\"Test\"")).isEqualTo("3Test");
48
+
}
49
+
50
+
@Test
51
+
void testInnerNumberConcatenation() throws ParseException, EvaluationException {
52
+
assertThat(evaluate("\"Start\"+1+1+\"End\"")).isEqualTo("Start11End");
53
+
}
54
+
55
+
@Test
56
+
void testInnerTripleNumberConcatenation() throws ParseException, EvaluationException {
57
+
assertThat(evaluate("\"Start\"+1+2+1+\"End\"")).isEqualTo("Start121End");
58
+
}
59
+
60
+
@Test
61
+
void testInnerNumberConcatenationWithBraces() throws ParseException, EvaluationException {
62
+
assertThat(evaluate("\"Start\"+(1+1)+\"End\"")).isEqualTo("Start2End");
63
+
}
64
+
}
+67
src/test/java/com/ezylang/evalex/ExpressionEvaluatorStructureTest.java
+67
src/test/java/com/ezylang/evalex/ExpressionEvaluatorStructureTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
20
+
21
+
import com.ezylang.evalex.parser.ParseException;
22
+
import java.math.BigDecimal;
23
+
import java.util.HashMap;
24
+
import java.util.Map;
25
+
import org.junit.jupiter.api.Test;
26
+
27
+
class ExpressionEvaluatorStructureTest extends BaseExpressionEvaluatorTest {
28
+
29
+
@Test
30
+
void testSimpleStructure() throws ParseException, EvaluationException {
31
+
Map<String, BigDecimal> structure =
32
+
new HashMap<>() {
33
+
{
34
+
put("b", new BigDecimal(99));
35
+
}
36
+
};
37
+
38
+
Expression expression = createExpression("a.b").with("a", structure);
39
+
40
+
assertThat(expression.evaluate().getStringValue()).isEqualTo("99");
41
+
}
42
+
43
+
@Test
44
+
void testTripleStructure() throws ParseException, EvaluationException {
45
+
Map<String, Map<String, BigDecimal>> structure = new HashMap<>();
46
+
47
+
Map<String, BigDecimal> subStructure =
48
+
new HashMap<>() {
49
+
{
50
+
put("c", new BigDecimal(95));
51
+
}
52
+
};
53
+
54
+
structure.put("b", subStructure);
55
+
56
+
Expression expression = createExpression("a.b.c").with("a", structure);
57
+
58
+
assertThat(expression.evaluate().getStringValue()).isEqualTo("95");
59
+
}
60
+
61
+
@Test
62
+
void testThrowsUnsupportedDataTypeForStructure() {
63
+
assertThatThrownBy(() -> createExpression("a.b").with("a", "aString").evaluate())
64
+
.isInstanceOf(EvaluationException.class)
65
+
.hasMessage("Unsupported data types in operation");
66
+
}
67
+
}
+148
src/test/java/com/ezylang/evalex/ExpressionTest.java
+148
src/test/java/com/ezylang/evalex/ExpressionTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
20
+
21
+
import com.ezylang.evalex.config.ExpressionConfiguration;
22
+
import com.ezylang.evalex.data.EvaluationValue;
23
+
import com.ezylang.evalex.parser.ASTNode;
24
+
import com.ezylang.evalex.parser.ParseException;
25
+
import java.math.MathContext;
26
+
import java.util.HashMap;
27
+
import java.util.List;
28
+
import java.util.Map;
29
+
import org.junit.jupiter.api.Test;
30
+
31
+
class ExpressionTest {
32
+
33
+
@Test
34
+
void testExpressionDefaults() {
35
+
Expression expression = new Expression("a+b");
36
+
37
+
assertThat(expression.getExpressionString()).isEqualTo("a+b");
38
+
assertThat(expression.getConfiguration().getMathContext())
39
+
.isEqualTo(ExpressionConfiguration.DEFAULT_MATH_CONTEXT);
40
+
assertThat(expression.getConfiguration().getFunctionDictionary().hasFunction("SUM")).isTrue();
41
+
assertThat(expression.getConfiguration().getOperatorDictionary().hasInfixOperator("+"))
42
+
.isTrue();
43
+
assertThat(expression.getConfiguration().getOperatorDictionary().hasPrefixOperator("+"))
44
+
.isTrue();
45
+
assertThat(expression.getConfiguration().getOperatorDictionary().hasPostfixOperator("+"))
46
+
.isFalse();
47
+
}
48
+
49
+
@Test
50
+
void testValidateOK() throws ParseException {
51
+
new Expression("1+1").validate();
52
+
}
53
+
54
+
@Test
55
+
void testValidateFail() {
56
+
assertThatThrownBy(() -> new Expression("2#3").validate())
57
+
.isInstanceOf(ParseException.class)
58
+
.hasMessage("Undefined operator '#'");
59
+
}
60
+
61
+
@Test
62
+
void testExpressionNode() throws ParseException, EvaluationException {
63
+
Expression expression = new Expression("a*b");
64
+
ASTNode subExpression = expression.createExpressionNode("4+3");
65
+
66
+
EvaluationValue result = expression.with("a", 2).and("b", subExpression).evaluate();
67
+
68
+
assertThat(result.getStringValue()).isEqualTo("14");
69
+
}
70
+
71
+
@Test
72
+
void testWithValues() throws ParseException, EvaluationException {
73
+
Expression expression = new Expression("(a + b) * (a - b)");
74
+
75
+
Map<String, Object> values = new HashMap<>();
76
+
values.put("a", 3.5);
77
+
values.put("b", 2.5);
78
+
79
+
EvaluationValue result = expression.withValues(values).evaluate();
80
+
81
+
assertThat(result.getStringValue()).isEqualTo("6");
82
+
}
83
+
84
+
@Test
85
+
void testDefaultExpressionOwnsOwnConfigurationEntries() {
86
+
Expression expression1 = new Expression("1+1");
87
+
Expression expression2 = new Expression("1+1");
88
+
89
+
assertThat(expression1.getDataAccessor()).isNotSameAs(expression2.getDataAccessor());
90
+
assertThat(expression1.getConfiguration().getOperatorDictionary())
91
+
.isNotSameAs(expression2.getConfiguration().getOperatorDictionary());
92
+
assertThat(expression1.getConfiguration().getFunctionDictionary())
93
+
.isNotSameAs(expression2.getConfiguration().getFunctionDictionary());
94
+
assertThat(expression1.getConfiguration().getDefaultConstants())
95
+
.isNotSameAs(expression2.getConfiguration().getDefaultConstants());
96
+
}
97
+
98
+
@Test
99
+
void testDoubleConverterDefaultMathContext() {
100
+
Expression defaultMathContextExpression = new Expression("1");
101
+
assertThat(defaultMathContextExpression.convertDoubleValue(1.67987654321).getNumberValue())
102
+
.isEqualByComparingTo("1.67987654321");
103
+
}
104
+
105
+
@Test
106
+
void testDoubleConverterLimitedMathContext() {
107
+
Expression limitedMathContextExpression =
108
+
new Expression(
109
+
"1", ExpressionConfiguration.builder().mathContext(new MathContext(3)).build());
110
+
assertThat(limitedMathContextExpression.convertDoubleValue(1.6789).getNumberValue())
111
+
.isEqualByComparingTo("1.68");
112
+
}
113
+
114
+
@Test
115
+
void testGetAllASTNodes() throws ParseException {
116
+
Expression expression = new Expression("1+2/3");
117
+
List<ASTNode> nodes = expression.getAllASTNodes();
118
+
assertThat(nodes.get(0).getToken().getValue()).isEqualTo("+");
119
+
assertThat(nodes.get(1).getToken().getValue()).isEqualTo("1");
120
+
assertThat(nodes.get(2).getToken().getValue()).isEqualTo("/");
121
+
assertThat(nodes.get(3).getToken().getValue()).isEqualTo("2");
122
+
assertThat(nodes.get(4).getToken().getValue()).isEqualTo("3");
123
+
}
124
+
125
+
@Test
126
+
void testGetUsedVariables() throws ParseException {
127
+
Expression expression = new Expression("a/2*PI+MIN(e,b)");
128
+
assertThat(expression.getUsedVariables()).containsExactlyInAnyOrder("a", "b");
129
+
}
130
+
131
+
@Test
132
+
void testGetUsedVariablesLongNames() throws ParseException {
133
+
Expression expression = new Expression("var1/2*PI+MIN(var2,var3)");
134
+
assertThat(expression.getUsedVariables()).containsExactlyInAnyOrder("var1", "var2", "var3");
135
+
}
136
+
137
+
@Test
138
+
void testGetUsedVariablesNoVariables() throws ParseException {
139
+
Expression expression = new Expression("1/2");
140
+
assertThat(expression.getUsedVariables()).isEmpty();
141
+
}
142
+
143
+
@Test
144
+
void testGetUsedVariablesCaseSensitivity() throws ParseException {
145
+
Expression expression = new Expression("a+B*b-A/PI*(1/2)*pi+e-E+a");
146
+
assertThat(expression.getUsedVariables()).containsExactlyInAnyOrder("a", "b");
147
+
}
148
+
}
+178
src/test/java/com/ezylang/evalex/config/ExpressionConfigurationTest.java
+178
src/test/java/com/ezylang/evalex/config/ExpressionConfigurationTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.config;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import com.ezylang.evalex.config.TestConfigurationProvider.DummyFunction;
21
+
import com.ezylang.evalex.data.DataAccessorIfc;
22
+
import com.ezylang.evalex.data.EvaluationValue;
23
+
import com.ezylang.evalex.data.MapBasedDataAccessor;
24
+
import com.ezylang.evalex.operators.OperatorIfc;
25
+
import com.ezylang.evalex.operators.arithmetic.InfixPlusOperator;
26
+
import java.math.MathContext;
27
+
import java.util.HashMap;
28
+
import java.util.Map;
29
+
import org.junit.jupiter.api.Test;
30
+
import org.mockito.Mockito;
31
+
32
+
class ExpressionConfigurationTest {
33
+
34
+
@Test
35
+
void testDefaultSetup() {
36
+
ExpressionConfiguration configuration = ExpressionConfiguration.defaultConfiguration();
37
+
38
+
assertThat(configuration.getMathContext())
39
+
.isEqualTo(ExpressionConfiguration.DEFAULT_MATH_CONTEXT);
40
+
assertThat(configuration.getOperatorDictionary())
41
+
.isInstanceOf(MapBasedOperatorDictionary.class);
42
+
assertThat(configuration.getFunctionDictionary())
43
+
.isInstanceOf(MapBasedFunctionDictionary.class);
44
+
assertThat(configuration.getDataAccessorSupplier().get())
45
+
.isInstanceOf(MapBasedDataAccessor.class);
46
+
assertThat(configuration.isArraysAllowed()).isTrue();
47
+
assertThat(configuration.isStructuresAllowed()).isTrue();
48
+
assertThat(configuration.isImplicitMultiplicationAllowed()).isTrue();
49
+
assertThat(configuration.getPowerOfPrecedence())
50
+
.isEqualTo(OperatorIfc.OPERATOR_PRECEDENCE_POWER);
51
+
assertThat(configuration.getDefaultConstants())
52
+
.containsAllEntriesOf(ExpressionConfiguration.StandardConstants);
53
+
assertThat(configuration.getDecimalPlacesRounding())
54
+
.isEqualTo(ExpressionConfiguration.DECIMAL_PLACES_ROUNDING_UNLIMITED);
55
+
assertThat(configuration.isStripTrailingZeros()).isTrue();
56
+
}
57
+
58
+
@Test
59
+
void testWithAdditionalOperators() {
60
+
ExpressionConfiguration configuration =
61
+
ExpressionConfiguration.defaultConfiguration()
62
+
.withAdditionalOperators(
63
+
Map.entry("ADDED1", new InfixPlusOperator()),
64
+
Map.entry("ADDED2", new InfixPlusOperator()));
65
+
66
+
assertThat(configuration.getOperatorDictionary().hasInfixOperator("ADDED1")).isTrue();
67
+
assertThat(configuration.getOperatorDictionary().hasInfixOperator("ADDED2")).isTrue();
68
+
}
69
+
70
+
@Test
71
+
void testWithAdditionalFunctions() {
72
+
ExpressionConfiguration configuration =
73
+
ExpressionConfiguration.defaultConfiguration()
74
+
.withAdditionalFunctions(
75
+
Map.entry("ADDED1", new DummyFunction()), Map.entry("ADDED2", new DummyFunction()));
76
+
77
+
assertThat(configuration.getFunctionDictionary().hasFunction("ADDED1")).isTrue();
78
+
assertThat(configuration.getFunctionDictionary().hasFunction("ADDED2")).isTrue();
79
+
}
80
+
81
+
@Test
82
+
void testCustomMathContext() {
83
+
ExpressionConfiguration configuration =
84
+
ExpressionConfiguration.builder().mathContext(MathContext.DECIMAL32).build();
85
+
86
+
assertThat(configuration.getMathContext()).isEqualTo(MathContext.DECIMAL32);
87
+
}
88
+
89
+
@Test
90
+
void testCustomOperatorDictionary() {
91
+
OperatorDictionaryIfc mockedOperatorDictionary = Mockito.mock(OperatorDictionaryIfc.class);
92
+
93
+
ExpressionConfiguration configuration =
94
+
ExpressionConfiguration.builder().operatorDictionary(mockedOperatorDictionary).build();
95
+
96
+
assertThat(configuration.getOperatorDictionary()).isEqualTo(mockedOperatorDictionary);
97
+
}
98
+
99
+
@Test
100
+
void testCustomFunctionDictionary() {
101
+
FunctionDictionaryIfc mockedFunctionDictionary = Mockito.mock(FunctionDictionaryIfc.class);
102
+
103
+
ExpressionConfiguration configuration =
104
+
ExpressionConfiguration.builder().functionDictionary(mockedFunctionDictionary).build();
105
+
106
+
assertThat(configuration.getFunctionDictionary()).isEqualTo(mockedFunctionDictionary);
107
+
}
108
+
109
+
@Test
110
+
void testCustomDataAccessorSupplier() {
111
+
DataAccessorIfc mockedDataAccessor = Mockito.mock(DataAccessorIfc.class);
112
+
113
+
ExpressionConfiguration configuration =
114
+
ExpressionConfiguration.builder().dataAccessorSupplier(() -> mockedDataAccessor).build();
115
+
116
+
assertThat(configuration.getDataAccessorSupplier().get()).isEqualTo(mockedDataAccessor);
117
+
}
118
+
119
+
@Test
120
+
void testDataAccessorSupplierReturnsNewInstance() {
121
+
ExpressionConfiguration configuration = ExpressionConfiguration.defaultConfiguration();
122
+
123
+
DataAccessorIfc accessor1 = configuration.getDataAccessorSupplier().get();
124
+
DataAccessorIfc accessor2 = configuration.getDataAccessorSupplier().get();
125
+
126
+
assertThat(accessor1).isNotEqualTo(accessor2);
127
+
}
128
+
129
+
@Test
130
+
void testCustomConstants() {
131
+
Map<String, EvaluationValue> constants =
132
+
new HashMap<>() {
133
+
{
134
+
put("A", new EvaluationValue("a"));
135
+
put("B", new EvaluationValue("b"));
136
+
}
137
+
};
138
+
ExpressionConfiguration configuration =
139
+
ExpressionConfiguration.builder().defaultConstants(constants).build();
140
+
141
+
assertThat(configuration.getDefaultConstants()).containsAllEntriesOf(constants);
142
+
}
143
+
144
+
@Test
145
+
void testArraysAllowed() {
146
+
ExpressionConfiguration configuration =
147
+
ExpressionConfiguration.builder().arraysAllowed(false).build();
148
+
149
+
assertThat(configuration.isArraysAllowed()).isFalse();
150
+
}
151
+
152
+
@Test
153
+
void testStructuresAllowed() {
154
+
ExpressionConfiguration configuration =
155
+
ExpressionConfiguration.builder().structuresAllowed(false).build();
156
+
157
+
assertThat(configuration.isStructuresAllowed()).isFalse();
158
+
}
159
+
160
+
@Test
161
+
void testImplicitMultiplicationAllowed() {
162
+
ExpressionConfiguration configuration =
163
+
ExpressionConfiguration.builder().implicitMultiplicationAllowed(false).build();
164
+
165
+
assertThat(configuration.isImplicitMultiplicationAllowed()).isFalse();
166
+
}
167
+
168
+
@Test
169
+
void testPowerOfPrecedence() {
170
+
ExpressionConfiguration configuration =
171
+
ExpressionConfiguration.builder()
172
+
.powerOfPrecedence(OperatorIfc.OPERATOR_PRECEDENCE_POWER_HIGHER)
173
+
.build();
174
+
175
+
assertThat(configuration.getPowerOfPrecedence())
176
+
.isEqualTo(OperatorIfc.OPERATOR_PRECEDENCE_POWER_HIGHER);
177
+
}
178
+
}
+62
src/test/java/com/ezylang/evalex/config/MapBasedFunctionDictionaryTest.java
+62
src/test/java/com/ezylang/evalex/config/MapBasedFunctionDictionaryTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.config;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import com.ezylang.evalex.functions.FunctionIfc;
21
+
import com.ezylang.evalex.functions.basic.MaxFunction;
22
+
import com.ezylang.evalex.functions.basic.MinFunction;
23
+
import java.util.Map;
24
+
import org.junit.jupiter.api.Test;
25
+
26
+
class MapBasedFunctionDictionaryTest {
27
+
28
+
@Test
29
+
void testCreationOfFunctions() {
30
+
FunctionIfc min = new MinFunction();
31
+
FunctionIfc max = new MaxFunction();
32
+
33
+
@SuppressWarnings({"unchecked", "varargs"})
34
+
FunctionDictionaryIfc dictionary =
35
+
MapBasedFunctionDictionary.ofFunctions(Map.entry("min", min), Map.entry("max", max));
36
+
37
+
assertThat(dictionary.hasFunction("min")).isTrue();
38
+
assertThat(dictionary.hasFunction("max")).isTrue();
39
+
40
+
assertThat(dictionary.getFunction("min")).isEqualTo(min);
41
+
assertThat(dictionary.getFunction("max")).isEqualTo(max);
42
+
43
+
assertThat(dictionary.hasFunction("medium")).isFalse();
44
+
}
45
+
46
+
@Test
47
+
void testCaseInsensitivity() {
48
+
FunctionIfc min = new MinFunction();
49
+
FunctionIfc max = new MaxFunction();
50
+
51
+
@SuppressWarnings({"unchecked", "varargs"})
52
+
FunctionDictionaryIfc dictionary =
53
+
MapBasedFunctionDictionary.ofFunctions(Map.entry("Min", min), Map.entry("MAX", max));
54
+
55
+
assertThat(dictionary.hasFunction("min")).isTrue();
56
+
assertThat(dictionary.hasFunction("MIN")).isTrue();
57
+
assertThat(dictionary.hasFunction("Min")).isTrue();
58
+
assertThat(dictionary.hasFunction("max")).isTrue();
59
+
assertThat(dictionary.hasFunction("MAX")).isTrue();
60
+
assertThat(dictionary.hasFunction("Max")).isTrue();
61
+
}
62
+
}
+78
src/test/java/com/ezylang/evalex/config/MapBasedOperatorDictionaryTest.java
+78
src/test/java/com/ezylang/evalex/config/MapBasedOperatorDictionaryTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.config;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import com.ezylang.evalex.config.TestConfigurationProvider.PostfixQuestionOperator;
21
+
import com.ezylang.evalex.config.TestConfigurationProvider.PrefixPlusPlusOperator;
22
+
import com.ezylang.evalex.operators.OperatorIfc;
23
+
import com.ezylang.evalex.operators.arithmetic.InfixModuloOperator;
24
+
import java.util.Map;
25
+
import org.junit.jupiter.api.Test;
26
+
27
+
class MapBasedOperatorDictionaryTest {
28
+
29
+
@Test
30
+
void testCreationOfOperators() {
31
+
OperatorIfc prefix = new PrefixPlusPlusOperator();
32
+
OperatorIfc postfix = new PostfixQuestionOperator();
33
+
OperatorIfc infix = new InfixModuloOperator();
34
+
35
+
@SuppressWarnings({"unchecked", "varargs"})
36
+
OperatorDictionaryIfc dictionary =
37
+
MapBasedOperatorDictionary.ofOperators(
38
+
Map.entry("++", prefix), Map.entry("?", postfix), Map.entry("%", infix));
39
+
40
+
assertThat(dictionary.hasPrefixOperator("++")).isTrue();
41
+
assertThat(dictionary.hasPostfixOperator("?")).isTrue();
42
+
assertThat(dictionary.hasInfixOperator("%")).isTrue();
43
+
44
+
assertThat(dictionary.getPrefixOperator("++")).isEqualTo(prefix);
45
+
assertThat(dictionary.getPostfixOperator("?")).isEqualTo(postfix);
46
+
assertThat(dictionary.getInfixOperator("%")).isEqualTo(infix);
47
+
48
+
assertThat(dictionary.hasPrefixOperator("A")).isFalse();
49
+
assertThat(dictionary.hasPostfixOperator("B")).isFalse();
50
+
assertThat(dictionary.hasInfixOperator("C")).isFalse();
51
+
}
52
+
53
+
@Test
54
+
void testCaseInsensitivity() {
55
+
OperatorIfc prefix = new PrefixPlusPlusOperator();
56
+
OperatorIfc postfix = new PostfixQuestionOperator();
57
+
OperatorIfc infix = new InfixModuloOperator();
58
+
59
+
@SuppressWarnings({"unchecked", "varargs"})
60
+
OperatorDictionaryIfc dictionary =
61
+
MapBasedOperatorDictionary.ofOperators(
62
+
Map.entry("PlusPlus", prefix),
63
+
Map.entry("Question", postfix),
64
+
Map.entry("Percent", infix));
65
+
66
+
assertThat(dictionary.hasPrefixOperator("PlusPlus")).isTrue();
67
+
assertThat(dictionary.hasPrefixOperator("plusplus")).isTrue();
68
+
assertThat(dictionary.hasPrefixOperator("PLUSPLUS")).isTrue();
69
+
70
+
assertThat(dictionary.hasPostfixOperator("Question")).isTrue();
71
+
assertThat(dictionary.hasPostfixOperator("question")).isTrue();
72
+
assertThat(dictionary.hasPostfixOperator("QUESTION")).isTrue();
73
+
74
+
assertThat(dictionary.hasInfixOperator("Percent")).isTrue();
75
+
assertThat(dictionary.hasInfixOperator("percent")).isTrue();
76
+
assertThat(dictionary.hasInfixOperator("PERCENT")).isTrue();
77
+
}
78
+
}
+80
src/test/java/com/ezylang/evalex/config/TestConfigurationProvider.java
+80
src/test/java/com/ezylang/evalex/config/TestConfigurationProvider.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.config;
17
+
18
+
import com.ezylang.evalex.Expression;
19
+
import com.ezylang.evalex.data.EvaluationValue;
20
+
import com.ezylang.evalex.functions.AbstractFunction;
21
+
import com.ezylang.evalex.functions.FunctionParameter;
22
+
import com.ezylang.evalex.operators.AbstractOperator;
23
+
import com.ezylang.evalex.operators.PostfixOperator;
24
+
import com.ezylang.evalex.operators.PrefixOperator;
25
+
import com.ezylang.evalex.parser.Token;
26
+
import java.math.BigDecimal;
27
+
import java.util.Map;
28
+
29
+
public class TestConfigurationProvider {
30
+
31
+
public static final ExpressionConfiguration StandardConfigurationWithAdditionalTestOperators =
32
+
ExpressionConfiguration.defaultConfiguration()
33
+
.withAdditionalOperators(
34
+
Map.entry("++", new PrefixPlusPlusOperator()),
35
+
Map.entry("++", new PostfixPlusPlusOperator()),
36
+
Map.entry("?", new PostfixQuestionOperator()))
37
+
.withAdditionalFunctions(Map.entry("TEST", new DummyFunction()));
38
+
39
+
@FunctionParameter(name = "input", isVarArg = true)
40
+
public static class DummyFunction extends AbstractFunction {
41
+
@Override
42
+
public EvaluationValue evaluate(
43
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
44
+
// dummy implementation
45
+
return new EvaluationValue("OK");
46
+
}
47
+
}
48
+
49
+
@PrefixOperator(leftAssociative = false)
50
+
public static class PrefixPlusPlusOperator extends AbstractOperator {
51
+
@Override
52
+
public EvaluationValue evaluate(
53
+
Expression expression, Token operatorToken, EvaluationValue... operands) {
54
+
// dummy implementation
55
+
EvaluationValue operand = operands[0];
56
+
return new EvaluationValue(operand.getNumberValue().add(BigDecimal.ONE));
57
+
}
58
+
}
59
+
60
+
@PostfixOperator()
61
+
public static class PostfixPlusPlusOperator extends AbstractOperator {
62
+
@Override
63
+
public EvaluationValue evaluate(
64
+
Expression expression, Token operatorToken, EvaluationValue... operands) {
65
+
// dummy implementation
66
+
EvaluationValue operand = operands[0];
67
+
return new EvaluationValue(operand.getNumberValue().add(BigDecimal.ONE));
68
+
}
69
+
}
70
+
71
+
@PostfixOperator(leftAssociative = false)
72
+
public static class PostfixQuestionOperator extends AbstractOperator {
73
+
@Override
74
+
public EvaluationValue evaluate(
75
+
Expression expression, Token operatorToken, EvaluationValue... operands) {
76
+
// dummy implementation
77
+
return new EvaluationValue("?");
78
+
}
79
+
}
80
+
}
+257
src/test/java/com/ezylang/evalex/data/EvaluationValueTest.java
+257
src/test/java/com/ezylang/evalex/data/EvaluationValueTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.data;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
20
+
21
+
import com.ezylang.evalex.parser.ASTNode;
22
+
import com.ezylang.evalex.parser.Token;
23
+
import com.ezylang.evalex.parser.Token.TokenType;
24
+
import java.math.BigDecimal;
25
+
import java.math.MathContext;
26
+
import java.util.Arrays;
27
+
import java.util.HashMap;
28
+
import java.util.List;
29
+
import java.util.Locale;
30
+
import java.util.Map;
31
+
import org.junit.jupiter.api.Test;
32
+
33
+
class EvaluationValueTest {
34
+
35
+
@Test
36
+
void testUnsupportedDataType() {
37
+
assertThatThrownBy(() -> new EvaluationValue(Locale.FRANCE))
38
+
.isInstanceOf(IllegalArgumentException.class)
39
+
.hasMessage("Unsupported data type 'java.util.Locale'");
40
+
}
41
+
42
+
@Test
43
+
void testString() {
44
+
EvaluationValue value = new EvaluationValue("Hello World");
45
+
46
+
assertThat(value.isStringValue()).isTrue();
47
+
assertDataIsCorrect(value, "Hello World", BigDecimal.ZERO, false, String.class);
48
+
}
49
+
50
+
@Test
51
+
void testStringBuilder() {
52
+
EvaluationValue value = new EvaluationValue(new StringBuilder("Hello StringBuilder World"));
53
+
54
+
assertThat(value.isStringValue()).isTrue();
55
+
assertDataIsCorrect(value, "Hello StringBuilder World", BigDecimal.ZERO, false, String.class);
56
+
}
57
+
58
+
@Test
59
+
void tesSCharacter() {
60
+
EvaluationValue value = new EvaluationValue('a');
61
+
62
+
assertThat(value.isStringValue()).isTrue();
63
+
assertDataIsCorrect(value, "a", BigDecimal.ZERO, false, String.class);
64
+
}
65
+
66
+
@Test
67
+
void testBooleanTrue() {
68
+
EvaluationValue value = new EvaluationValue(true);
69
+
70
+
assertThat(value.isBooleanValue()).isTrue();
71
+
assertDataIsCorrect(value, "true", BigDecimal.ONE, true, Boolean.class);
72
+
}
73
+
74
+
@Test
75
+
void testBooleanFalse() {
76
+
EvaluationValue value = new EvaluationValue(false);
77
+
78
+
assertThat(value.isBooleanValue()).isTrue();
79
+
assertDataIsCorrect(value, "false", BigDecimal.ZERO, false, Boolean.class);
80
+
}
81
+
82
+
@Test
83
+
void testBooleanString() {
84
+
EvaluationValue value = new EvaluationValue("true");
85
+
86
+
assertThat(value.isStringValue()).isTrue();
87
+
assertDataIsCorrect(value, "true", BigDecimal.ONE, true, String.class);
88
+
}
89
+
90
+
@Test
91
+
void testBooleanNumberZero() {
92
+
EvaluationValue value = new EvaluationValue(BigDecimal.ZERO);
93
+
94
+
assertThat(value.isNumberValue()).isTrue();
95
+
assertDataIsCorrect(value, "0", BigDecimal.ZERO, false, BigDecimal.class);
96
+
}
97
+
98
+
@Test
99
+
void testBigDecimal() {
100
+
EvaluationValue value = new EvaluationValue(new BigDecimal("123.5"));
101
+
102
+
assertThat(value.isNumberValue()).isTrue();
103
+
assertDataIsCorrect(value, "123.5", new BigDecimal("123.5"), true, BigDecimal.class);
104
+
}
105
+
106
+
@Test
107
+
void testFloat() {
108
+
EvaluationValue value = new EvaluationValue((float) 4.5);
109
+
110
+
assertThat(value.isNumberValue()).isTrue();
111
+
assertDataIsCorrect(value, "4.5", BigDecimal.valueOf((float) 4.5), true, BigDecimal.class);
112
+
}
113
+
114
+
@Test
115
+
void testDouble() {
116
+
EvaluationValue value = new EvaluationValue(8.5);
117
+
118
+
assertThat(value.isNumberValue()).isTrue();
119
+
assertDataIsCorrect(value, "8.5", BigDecimal.valueOf(8.5), true, BigDecimal.class);
120
+
}
121
+
122
+
@Test
123
+
void testLong() {
124
+
EvaluationValue value = new EvaluationValue(6L);
125
+
126
+
assertThat(value.isNumberValue()).isTrue();
127
+
assertDataIsCorrect(value, "6", new BigDecimal(6), true, BigDecimal.class);
128
+
}
129
+
130
+
@Test
131
+
void testInteger() {
132
+
EvaluationValue value = new EvaluationValue(5);
133
+
134
+
assertThat(value.isNumberValue()).isTrue();
135
+
assertDataIsCorrect(value, "5", new BigDecimal(5), true, BigDecimal.class);
136
+
}
137
+
138
+
@Test
139
+
void testShort() {
140
+
EvaluationValue value = new EvaluationValue((short) 4);
141
+
142
+
assertThat(value.isNumberValue()).isTrue();
143
+
assertDataIsCorrect(value, "4", new BigDecimal(4), true, BigDecimal.class);
144
+
}
145
+
146
+
@Test
147
+
void testByte() {
148
+
EvaluationValue value = new EvaluationValue((byte) 3);
149
+
150
+
assertThat(value.isNumberValue()).isTrue();
151
+
assertDataIsCorrect(value, "3", new BigDecimal(3), true, BigDecimal.class);
152
+
}
153
+
154
+
@Test
155
+
void testArray() {
156
+
EvaluationValue value =
157
+
new EvaluationValue(Arrays.asList(new BigDecimal(1), new BigDecimal(2)));
158
+
159
+
assertThat(value.isArrayValue()).isTrue();
160
+
161
+
assertThat(value.getArrayValue()).hasSize(2);
162
+
assertThat(value.getArrayValue().get(0).getStringValue()).isEqualTo("1");
163
+
assertThat(value.getArrayValue().get(1).getStringValue()).isEqualTo("2");
164
+
165
+
assertThat(value.getValue()).isInstanceOf(List.class);
166
+
}
167
+
168
+
@Test
169
+
void testArrayEmpty() {
170
+
EvaluationValue value = new EvaluationValue(new BigDecimal(1));
171
+
172
+
assertThat(value.getArrayValue()).isEmpty();
173
+
}
174
+
175
+
@Test
176
+
void testStructure() {
177
+
Map<String, Object> structure = new HashMap<>();
178
+
structure.put("a", "Hello");
179
+
structure.put("b", new BigDecimal(99));
180
+
EvaluationValue value = new EvaluationValue(structure);
181
+
182
+
assertThat(value.isStructureValue()).isTrue();
183
+
184
+
assertThat(value.getStructureValue()).hasSize(2);
185
+
assertThat(value.getStructureValue().get("a").getStringValue()).isEqualTo("Hello");
186
+
assertThat(value.getStructureValue().get("b").getStringValue()).isEqualTo("99");
187
+
188
+
assertThat(value.getValue()).isInstanceOf(Map.class);
189
+
}
190
+
191
+
@Test
192
+
void testStructureEmpty() {
193
+
EvaluationValue value = new EvaluationValue(new BigDecimal(1));
194
+
195
+
assertThat(value.getStructureValue()).isEmpty();
196
+
}
197
+
198
+
@Test
199
+
void testExpressionNode() {
200
+
ASTNode node = new ASTNode(new Token(1, "a", TokenType.VARIABLE_OR_CONSTANT));
201
+
EvaluationValue value = new EvaluationValue(node);
202
+
203
+
assertThat(value.isExpressionNode()).isTrue();
204
+
205
+
assertDataIsCorrect(
206
+
value,
207
+
"ASTNode(parameters=[], token=Token(startPosition=1, value=a, type=VARIABLE_OR_CONSTANT))",
208
+
BigDecimal.ZERO,
209
+
false,
210
+
ASTNode.class);
211
+
}
212
+
213
+
@Test
214
+
void testNumberOfString() {
215
+
EvaluationValue value = EvaluationValue.numberOfString("123.987", MathContext.DECIMAL128);
216
+
217
+
assertThat(value.isNumberValue()).isTrue();
218
+
assertThat(value.getNumberValue()).isEqualTo(new BigDecimal("123.987", MathContext.DECIMAL128));
219
+
assertThat(value).hasToString("EvaluationValue(value=123.987, dataType=NUMBER)");
220
+
}
221
+
222
+
@Test
223
+
void testCompare() {
224
+
assertThat(new EvaluationValue("Hello")).isEqualByComparingTo(new EvaluationValue("Hello"));
225
+
assertThat(new EvaluationValue(123)).isEqualByComparingTo(new EvaluationValue(123));
226
+
assertThat(new EvaluationValue(true)).isEqualByComparingTo(new EvaluationValue(true));
227
+
228
+
assertThat(new EvaluationValue("Hello")).isGreaterThan(new EvaluationValue("Hell"));
229
+
assertThat(new EvaluationValue(124)).isGreaterThan(new EvaluationValue(123));
230
+
assertThat(new EvaluationValue(true)).isGreaterThan(new EvaluationValue(false));
231
+
232
+
assertThat(new EvaluationValue("Hell")).isLessThan(new EvaluationValue("Hello"));
233
+
assertThat(new EvaluationValue(123)).isLessThan(new EvaluationValue(124));
234
+
assertThat(new EvaluationValue(false)).isLessThan(new EvaluationValue(true));
235
+
}
236
+
237
+
@Test
238
+
void testDoubleMathContext() {
239
+
assertThat(new EvaluationValue(3.9876, MathContext.DECIMAL64).getNumberValue())
240
+
.isEqualByComparingTo("3.9876");
241
+
242
+
assertThat(new EvaluationValue(3.9876, new MathContext(3)).getNumberValue())
243
+
.isEqualByComparingTo("3.99");
244
+
}
245
+
246
+
private void assertDataIsCorrect(
247
+
EvaluationValue value,
248
+
String stringValue,
249
+
BigDecimal numberValue,
250
+
Boolean booleanValue,
251
+
Class<?> valueInstance) {
252
+
assertThat(value.getStringValue()).isEqualTo(stringValue);
253
+
assertThat(value.getNumberValue()).isEqualTo(numberValue);
254
+
assertThat(value.getBooleanValue()).isEqualTo(booleanValue);
255
+
assertThat(value.getValue()).isInstanceOf(valueInstance);
256
+
}
257
+
}
+53
src/test/java/com/ezylang/evalex/data/MapBasedDataAccessorTest.java
+53
src/test/java/com/ezylang/evalex/data/MapBasedDataAccessorTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.data;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import java.math.BigDecimal;
21
+
import org.junit.jupiter.api.Test;
22
+
23
+
class MapBasedDataAccessorTest {
24
+
25
+
@Test
26
+
void testSetGetData() {
27
+
DataAccessorIfc dataAccessor = new MapBasedDataAccessor();
28
+
29
+
EvaluationValue num = new EvaluationValue(new BigDecimal("123"));
30
+
EvaluationValue string = new EvaluationValue("hello");
31
+
EvaluationValue bool = new EvaluationValue(true);
32
+
33
+
dataAccessor.setData("num", num);
34
+
dataAccessor.setData("string", string);
35
+
dataAccessor.setData("bool", bool);
36
+
37
+
assertThat(dataAccessor.getData("num")).isEqualTo(num);
38
+
assertThat(dataAccessor.getData("string")).isEqualTo(string);
39
+
assertThat(dataAccessor.getData("bool")).isEqualTo(bool);
40
+
}
41
+
42
+
@Test
43
+
void testCaseInsensitivity() {
44
+
DataAccessorIfc dataAccessor = new MapBasedDataAccessor();
45
+
46
+
EvaluationValue num = new EvaluationValue(new BigDecimal("123"));
47
+
dataAccessor.setData("Hello", num);
48
+
49
+
assertThat(dataAccessor.getData("Hello")).isEqualTo(num);
50
+
assertThat(dataAccessor.getData("hello")).isEqualTo(num);
51
+
assertThat(dataAccessor.getData("HELLO")).isEqualTo(num);
52
+
}
53
+
}
+46
src/test/java/com/ezylang/evalex/functions/FunctionParameterDefinitionTest.java
+46
src/test/java/com/ezylang/evalex/functions/FunctionParameterDefinitionTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import org.junit.jupiter.api.Test;
21
+
22
+
class FunctionParameterDefinitionTest {
23
+
24
+
@Test
25
+
void testCreation() {
26
+
FunctionParameterDefinition definition =
27
+
FunctionParameterDefinition.builder()
28
+
.name("name")
29
+
.isVarArg(true)
30
+
.isLazy(true)
31
+
.nonZero(true)
32
+
.nonNegative(true)
33
+
.build();
34
+
35
+
assertThat(definition.getName()).isEqualTo("name");
36
+
assertThat(definition.isVarArg()).isTrue();
37
+
assertThat(definition.isLazy()).isTrue();
38
+
assertThat(definition.isNonZero()).isTrue();
39
+
assertThat(definition.isNonNegative()).isTrue();
40
+
41
+
assertThat(definition)
42
+
.hasToString(
43
+
"FunctionParameterDefinition(name=name, isVarArg=true, isLazy=true, nonZero=true,"
44
+
+ " nonNegative=true)");
45
+
}
46
+
}
+84
src/test/java/com/ezylang/evalex/functions/FunctionTest.java
+84
src/test/java/com/ezylang/evalex/functions/FunctionTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
20
+
21
+
import com.ezylang.evalex.Expression;
22
+
import com.ezylang.evalex.data.EvaluationValue;
23
+
import com.ezylang.evalex.parser.Token;
24
+
import org.junit.jupiter.api.Test;
25
+
26
+
class FunctionTest {
27
+
28
+
@Test
29
+
void testParameterDefinition() {
30
+
FunctionIfc function = new CorrectFunctionDefinitionFunction();
31
+
32
+
assertThat(function.getFunctionParameterDefinitions().get(0).getName()).isEqualTo("default");
33
+
assertThat(function.getFunctionParameterDefinitions().get(0).isLazy()).isFalse();
34
+
assertThat(function.getFunctionParameterDefinitions().get(0).isVarArg()).isFalse();
35
+
36
+
assertThat(function.getFunctionParameterDefinitions().get(1).getName()).isEqualTo("lazy");
37
+
assertThat(function.getFunctionParameterDefinitions().get(1).isLazy()).isTrue();
38
+
assertThat(function.getFunctionParameterDefinitions().get(1).isVarArg()).isFalse();
39
+
40
+
assertThat(function.getFunctionParameterDefinitions().get(2).getName()).isEqualTo("vararg");
41
+
assertThat(function.getFunctionParameterDefinitions().get(2).isLazy()).isFalse();
42
+
assertThat(function.getFunctionParameterDefinitions().get(2).isVarArg()).isTrue();
43
+
}
44
+
45
+
@Test
46
+
void testParameterIsLazy() {
47
+
FunctionIfc function = new CorrectFunctionDefinitionFunction();
48
+
49
+
assertThat(function.isParameterLazy(0)).isFalse();
50
+
assertThat(function.isParameterLazy(1)).isTrue();
51
+
assertThat(function.isParameterLazy(2)).isFalse();
52
+
assertThat(function.isParameterLazy(3)).isFalse();
53
+
assertThat(function.isParameterLazy(4)).isFalse();
54
+
}
55
+
56
+
@Test
57
+
void testVarargNotAllowed() {
58
+
assertThatThrownBy(WrongVarargFunctionDefinitionFunction::new)
59
+
.isInstanceOf(IllegalArgumentException.class)
60
+
.hasMessage("Only last parameter may be defined as variable argument");
61
+
}
62
+
63
+
@FunctionParameter(name = "default")
64
+
@FunctionParameter(name = "lazy", isLazy = true)
65
+
@FunctionParameter(name = "vararg", isVarArg = true)
66
+
private static class CorrectFunctionDefinitionFunction extends AbstractFunction {
67
+
@Override
68
+
public EvaluationValue evaluate(
69
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
70
+
return new EvaluationValue("OK");
71
+
}
72
+
}
73
+
74
+
@FunctionParameter(name = "default")
75
+
@FunctionParameter(name = "vararg", isVarArg = true)
76
+
@FunctionParameter(name = "another")
77
+
private static class WrongVarargFunctionDefinitionFunction extends AbstractFunction {
78
+
@Override
79
+
public EvaluationValue evaluate(
80
+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
81
+
return new EvaluationValue("OK");
82
+
}
83
+
}
84
+
}
+297
src/test/java/com/ezylang/evalex/functions/basic/BasicFunctionsTest.java
+297
src/test/java/com/ezylang/evalex/functions/basic/BasicFunctionsTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.basic;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
20
+
21
+
import com.ezylang.evalex.BaseEvaluationTest;
22
+
import com.ezylang.evalex.EvaluationException;
23
+
import com.ezylang.evalex.Expression;
24
+
import com.ezylang.evalex.config.ExpressionConfiguration;
25
+
import com.ezylang.evalex.data.EvaluationValue;
26
+
import com.ezylang.evalex.parser.ParseException;
27
+
import java.math.MathContext;
28
+
import java.math.RoundingMode;
29
+
import org.junit.jupiter.api.Test;
30
+
import org.junit.jupiter.params.ParameterizedTest;
31
+
import org.junit.jupiter.params.provider.CsvSource;
32
+
33
+
class BasicFunctionsTest extends BaseEvaluationTest {
34
+
35
+
@ParameterizedTest
36
+
@CsvSource(
37
+
delimiter = ':',
38
+
value = {
39
+
"FACT(0) : 1",
40
+
"FACT(1) : 1",
41
+
"FACT(2) : 2",
42
+
"FACT(3) : 6",
43
+
"FACT(5) : 120",
44
+
"FACT(10) : 3628800",
45
+
"FACT(20) : 2432902008176640000"
46
+
})
47
+
void testFactorial(String expression, String expectedResult)
48
+
throws EvaluationException, ParseException {
49
+
assertExpressionHasExpectedResult(expression, expectedResult);
50
+
}
51
+
52
+
@ParameterizedTest
53
+
@CsvSource(
54
+
delimiter = ':',
55
+
value = {
56
+
"IF(1, 4/2, 4/0) : 2",
57
+
"IF(1, 4/IF(0, 5/0, 2*2), 4/0) : 1",
58
+
"IF(1, 6/IF(0, 5/0, 2*IF(1, 3, 6/0)), 4/0) : 1"
59
+
})
60
+
void testIf(String expression, String expectedResult) throws EvaluationException, ParseException {
61
+
assertExpressionHasExpectedResult(expression, expectedResult);
62
+
}
63
+
64
+
@ParameterizedTest
65
+
@CsvSource(
66
+
delimiter = ':',
67
+
value = {
68
+
"MAX(99) : 99",
69
+
"MAX(2,1) : 2",
70
+
"MAX(1,9,-5,6,3,7) : 9",
71
+
"MAX(17,88,77,66,609,1567,1876534) : 1876534"
72
+
})
73
+
void testMax(String expression, String expectedResult)
74
+
throws EvaluationException, ParseException {
75
+
assertExpressionHasExpectedResult(expression, expectedResult);
76
+
}
77
+
78
+
@ParameterizedTest
79
+
@CsvSource(
80
+
delimiter = ':',
81
+
value = {
82
+
"MIN(99) : 99",
83
+
"MIN(2,1) : 1",
84
+
"MIN(1,9,-5,6,3,7) : -5",
85
+
"MIN(17,88,77,66,609,1567,1876534) : 17"
86
+
})
87
+
void testMin(String expression, String expectedResult)
88
+
throws EvaluationException, ParseException {
89
+
assertExpressionHasExpectedResult(expression, expectedResult);
90
+
}
91
+
92
+
@ParameterizedTest
93
+
@CsvSource(
94
+
delimiter = ':',
95
+
value = {
96
+
"ROUND(1.1,0) : 1",
97
+
"ROUND(1.5,0) : 2",
98
+
"ROUND(2.34,1) : 2.3",
99
+
"ROUND(2.35,1) : 2.4",
100
+
"ROUND(2.323789,2) : 2.32",
101
+
"ROUND(2.324789,2) : 2.32"
102
+
})
103
+
void testRoundHalfEven(String expression, String expectedResult)
104
+
throws EvaluationException, ParseException {
105
+
assertExpressionHasExpectedResult(expression, expectedResult);
106
+
}
107
+
108
+
@ParameterizedTest
109
+
@CsvSource(
110
+
delimiter = ':',
111
+
value = {
112
+
"ROUND(1.1,0) : 2",
113
+
"ROUND(1.5,0) : 2",
114
+
"ROUND(2.34,1) : 2.4",
115
+
"ROUND(2.35,1) : 2.4",
116
+
"ROUND(2.323789,2) : 2.33",
117
+
"ROUND(2.324789,2) : 2.33"
118
+
})
119
+
void testRoundUp(String expression, String expectedResult)
120
+
throws EvaluationException, ParseException {
121
+
ExpressionConfiguration configuration =
122
+
ExpressionConfiguration.builder().mathContext(new MathContext(32, RoundingMode.UP)).build();
123
+
assertExpressionHasExpectedResult(expression, expectedResult, configuration);
124
+
}
125
+
126
+
@ParameterizedTest
127
+
@CsvSource(
128
+
delimiter = ':',
129
+
value = {
130
+
"SUM(1) : 1",
131
+
"SUM(1,2,3,4) : 10",
132
+
"SUM(1,-1) : 0",
133
+
"SUM(1,10,100,1000,10000) : 11111",
134
+
"SUM(1,2,3,-3,-2,5) : 6"
135
+
})
136
+
void testSum(String expression, String expectedResult)
137
+
throws EvaluationException, ParseException {
138
+
assertExpressionHasExpectedResult(expression, expectedResult);
139
+
}
140
+
141
+
@ParameterizedTest
142
+
@CsvSource(
143
+
delimiter = ':',
144
+
value = {
145
+
"SQRT(0) : 0",
146
+
"SQRT(1) : 1",
147
+
"SQRT(2) : 1.41421356237309504880168872420969807856967187537694807317667973799073",
148
+
"SQRT(4) : 2",
149
+
"SQRT(5) : 2.23606797749978969640917366873127623544061835961152572427089724541052",
150
+
"SQRT(10) : 3.16227766016837933199889354443271853371955513932521682685750485279259",
151
+
"SQRT(236769) : 486.58914907753543122473972072155030396245230523850016876894122736411182"
152
+
})
153
+
void testSqrt(String expression, String expectedResult)
154
+
throws EvaluationException, ParseException {
155
+
assertExpressionHasExpectedResult(expression, expectedResult);
156
+
}
157
+
158
+
@Test
159
+
void testSqrtNegative() {
160
+
assertThatThrownBy(() -> new Expression("SQRT(-1)").evaluate())
161
+
.isInstanceOf(EvaluationException.class)
162
+
.hasMessage("Parameter must not be negative");
163
+
}
164
+
165
+
@ParameterizedTest
166
+
@CsvSource(
167
+
delimiter = ':',
168
+
value = {
169
+
"NOT(0) : true",
170
+
"NOT(1) : false",
171
+
"NOT(20) : false",
172
+
"NOT(\"true\") : false",
173
+
"NOT(\"false\") : true",
174
+
"NOT(2-4/2) : true",
175
+
})
176
+
void testNot(String expression, String expectedResult)
177
+
throws EvaluationException, ParseException {
178
+
assertExpressionHasExpectedResult(expression, expectedResult);
179
+
}
180
+
181
+
@Test
182
+
void testRandom() throws EvaluationException, ParseException {
183
+
EvaluationValue r1 = new Expression("RANDOM()").evaluate();
184
+
EvaluationValue r2 = new Expression("RANDOM()").evaluate();
185
+
186
+
assertThat(r1).isNotEqualByComparingTo(r2);
187
+
}
188
+
189
+
@ParameterizedTest
190
+
@CsvSource(
191
+
delimiter = ':',
192
+
value = {
193
+
"ABS(0) : 0",
194
+
"ABS(1) : 1",
195
+
"ABS(-1) : 1",
196
+
"ABS(20) : 20",
197
+
"ABS(-20) : 20",
198
+
"ABS(2.12345) : 2.12345",
199
+
"ABS(-2.12345) : 2.12345"
200
+
})
201
+
void testAbs(String expression, String expectedResult)
202
+
throws EvaluationException, ParseException {
203
+
assertExpressionHasExpectedResult(expression, expectedResult);
204
+
}
205
+
206
+
@ParameterizedTest
207
+
@CsvSource(
208
+
delimiter = ':',
209
+
value = {
210
+
"FLOOR(0) : 0",
211
+
"FLOOR(1) : 1",
212
+
"FLOOR(-1) : -1",
213
+
"FLOOR(20) : 20",
214
+
"FLOOR(-20) : -20",
215
+
"FLOOR(2.12345) : 2",
216
+
"FLOOR(-2.12345) : -3",
217
+
"FLOOR(-2.97345) : -3"
218
+
})
219
+
void testFloor(String expression, String expectedResult)
220
+
throws EvaluationException, ParseException {
221
+
assertExpressionHasExpectedResult(expression, expectedResult);
222
+
}
223
+
224
+
@ParameterizedTest
225
+
@CsvSource(
226
+
delimiter = ':',
227
+
value = {
228
+
"CEILING(0) : 0",
229
+
"CEILING(1) : 1",
230
+
"CEILING(-1) : -1",
231
+
"CEILING(20) : 20",
232
+
"CEILING(-20) : -20",
233
+
"CEILING(2.12345) : 3",
234
+
"CEILING(-2.12345) : -2",
235
+
"CEILING(-2.97345) : -2"
236
+
})
237
+
void testCeiling(String expression, String expectedResult)
238
+
throws EvaluationException, ParseException {
239
+
assertExpressionHasExpectedResult(expression, expectedResult);
240
+
}
241
+
242
+
@ParameterizedTest
243
+
@CsvSource(
244
+
delimiter = ':',
245
+
value = {
246
+
"LOG(1) : 0",
247
+
"LOG(10) : 2.302585092994046",
248
+
"LOG(2.12345) : 0.7530421244614831",
249
+
"LOG(1567) : 7.356918242356021"
250
+
})
251
+
void testLog(String expression, String expectedResult)
252
+
throws EvaluationException, ParseException {
253
+
assertExpressionHasExpectedResult(expression, expectedResult);
254
+
}
255
+
256
+
@Test
257
+
void testLogNegative() {
258
+
assertThatThrownBy(() -> new Expression("LOG(-1)").evaluate())
259
+
.isInstanceOf(EvaluationException.class)
260
+
.hasMessage("Parameter must not be negative");
261
+
}
262
+
263
+
@Test
264
+
void testLogZero() {
265
+
assertThatThrownBy(() -> new Expression("LOG(0)").evaluate())
266
+
.isInstanceOf(EvaluationException.class)
267
+
.hasMessage("Parameter must not be zero");
268
+
}
269
+
270
+
@ParameterizedTest
271
+
@CsvSource(
272
+
delimiter = ':',
273
+
value = {
274
+
"LOG10(1) : 0",
275
+
"LOG10(10) : 1",
276
+
"LOG10(2.12345) : 0.3270420392943239",
277
+
"LOG10(1567) : 3.1950689964685903"
278
+
})
279
+
void testLog10(String expression, String expectedResult)
280
+
throws EvaluationException, ParseException {
281
+
assertExpressionHasExpectedResult(expression, expectedResult);
282
+
}
283
+
284
+
@Test
285
+
void testLog10Negative() {
286
+
assertThatThrownBy(() -> new Expression("LOG10(-1)").evaluate())
287
+
.isInstanceOf(EvaluationException.class)
288
+
.hasMessage("Parameter must not be negative");
289
+
}
290
+
291
+
@Test
292
+
void testLog10Zero() {
293
+
assertThatThrownBy(() -> new Expression("LOG10(0)").evaluate())
294
+
.isInstanceOf(EvaluationException.class)
295
+
.hasMessage("Parameter must not be zero");
296
+
}
297
+
}
+74
src/test/java/com/ezylang/evalex/functions/string/StringFunctionsTest.java
+74
src/test/java/com/ezylang/evalex/functions/string/StringFunctionsTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.string;
17
+
18
+
import com.ezylang.evalex.BaseEvaluationTest;
19
+
import com.ezylang.evalex.EvaluationException;
20
+
import com.ezylang.evalex.parser.ParseException;
21
+
import org.junit.jupiter.params.ParameterizedTest;
22
+
import org.junit.jupiter.params.provider.CsvSource;
23
+
24
+
class StringFunctionsTest extends BaseEvaluationTest {
25
+
26
+
@ParameterizedTest
27
+
@CsvSource(
28
+
delimiter = ':',
29
+
value = {
30
+
"STR_UPPER(\"\") : ''",
31
+
"STR_UPPER(\"a\") : A",
32
+
"STR_UPPER(\"A\") : A",
33
+
"STR_UPPER(\"AbCdEf\") : ABCDEF",
34
+
"STR_UPPER(\"A1b3C4/?\") : A1B3C4/?",
35
+
"STR_UPPER(\"äöüß\") : ÄÖÜSS"
36
+
})
37
+
void testUpper(String expression, String expectedResult)
38
+
throws EvaluationException, ParseException {
39
+
assertExpressionHasExpectedResult(expression, expectedResult);
40
+
}
41
+
42
+
@ParameterizedTest
43
+
@CsvSource(
44
+
delimiter = ':',
45
+
value = {
46
+
"STR_LOWER(\"\") : ''",
47
+
"STR_LOWER(\"A\") : a",
48
+
"STR_LOWER(\"a\") : a",
49
+
"STR_LOWER(\"AbCdEf\") : abcdef",
50
+
"STR_LOWER(\"A1b3C4/?\") : a1b3c4/?",
51
+
"STR_LOWER(\"ÄÖÜSS\") : äöüss"
52
+
})
53
+
void testLower(String expression, String expectedResult)
54
+
throws EvaluationException, ParseException {
55
+
assertExpressionHasExpectedResult(expression, expectedResult);
56
+
}
57
+
58
+
@ParameterizedTest
59
+
@CsvSource(
60
+
delimiter = ':',
61
+
value = {
62
+
"STR_CONTAINS(\"\", \"\") : true",
63
+
"STR_CONTAINS(\"a\", \"a\") : true",
64
+
"STR_CONTAINS(\"Hello World\", \"Wor\") : true",
65
+
"STR_CONTAINS(\"What a world\", \"what\") : true",
66
+
"STR_CONTAINS(\"What a world\", \"a world\") : true",
67
+
"STR_CONTAINS(\"What a world\", \"moon\") : false",
68
+
"STR_CONTAINS(\"\", \"text\") : false"
69
+
})
70
+
void testContains(String expression, String expectedResult)
71
+
throws EvaluationException, ParseException {
72
+
assertExpressionHasExpectedResult(expression, expectedResult);
73
+
}
74
+
}
+566
src/test/java/com/ezylang/evalex/functions/trigonometric/TrigonometricFunctionsTest.java
+566
src/test/java/com/ezylang/evalex/functions/trigonometric/TrigonometricFunctionsTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.functions.trigonometric;
17
+
18
+
import com.ezylang.evalex.BaseEvaluationTest;
19
+
import com.ezylang.evalex.EvaluationException;
20
+
import com.ezylang.evalex.Expression;
21
+
import com.ezylang.evalex.parser.ParseException;
22
+
import org.assertj.core.api.Assertions;
23
+
import org.junit.jupiter.api.Test;
24
+
import org.junit.jupiter.params.ParameterizedTest;
25
+
import org.junit.jupiter.params.provider.CsvSource;
26
+
import org.junit.jupiter.params.provider.ValueSource;
27
+
28
+
class TrigonometricFunctionsTest extends BaseEvaluationTest {
29
+
30
+
@ParameterizedTest
31
+
@CsvSource(
32
+
delimiter = ':',
33
+
value = {
34
+
"DEG(0) : 0",
35
+
"DEG(1) : 57.29577951308232",
36
+
"DEG(90) : 5156.620156177409",
37
+
"DEG(-90) : -5156.620156177409"
38
+
})
39
+
void testDeg(String expression, String expectedResult)
40
+
throws EvaluationException, ParseException {
41
+
assertExpressionHasExpectedResult(expression, expectedResult);
42
+
}
43
+
44
+
@ParameterizedTest
45
+
@CsvSource(
46
+
delimiter = ':',
47
+
value = {
48
+
"RAD(0) : 0",
49
+
"RAD(1) : 0.017453292519943295",
50
+
"RAD(45) : 0.7853981633974483",
51
+
"RAD(50) : 0.8726646259971648",
52
+
"RAD(90) : 1.5707963267948966",
53
+
"RAD(-90) : -1.5707963267948966"
54
+
})
55
+
void testRad(String expression, String expectedResult)
56
+
throws EvaluationException, ParseException {
57
+
assertExpressionHasExpectedResult(expression, expectedResult);
58
+
}
59
+
60
+
@ParameterizedTest
61
+
@CsvSource(
62
+
delimiter = ':',
63
+
value = {"SIN(0) : 0", "SIN(1) : 0.01745240643728351", "SIN(90) : 1", "SIN(-90) : -1"})
64
+
void testSin(String expression, String expectedResult)
65
+
throws EvaluationException, ParseException {
66
+
assertExpressionHasExpectedResult(expression, expectedResult);
67
+
}
68
+
69
+
@ParameterizedTest
70
+
@CsvSource(
71
+
delimiter = ':',
72
+
value = {
73
+
"COS(0) : 1",
74
+
"COS(1) : 0.9998476951563913",
75
+
"COS(19) : 0.9455185755993168",
76
+
"COS(-19) : 0.9455185755993168"
77
+
})
78
+
void testCos(String expression, String expectedResult)
79
+
throws EvaluationException, ParseException {
80
+
assertExpressionHasExpectedResult(expression, expectedResult);
81
+
}
82
+
83
+
@ParameterizedTest
84
+
@CsvSource(
85
+
delimiter = ':',
86
+
value = {
87
+
"TAN(0) : 0",
88
+
"TAN(1) : 0.017455064928217585",
89
+
"TAN(19) : 0.34432761328966527",
90
+
"TAN(-19) : -0.34432761328966527"
91
+
})
92
+
void testTan(String expression, String expectedResult)
93
+
throws EvaluationException, ParseException {
94
+
assertExpressionHasExpectedResult(expression, expectedResult);
95
+
}
96
+
97
+
@ParameterizedTest
98
+
@CsvSource(
99
+
delimiter = ':',
100
+
value = {
101
+
"SINR(0) : 0",
102
+
"SINR(1) : 0.8414709848078965",
103
+
"SINR(90) : 0.8939966636005579",
104
+
"SINR(-90) : -0.8939966636005579"
105
+
})
106
+
void testSinR(String expression, String expectedResult)
107
+
throws EvaluationException, ParseException {
108
+
assertExpressionHasExpectedResult(expression, expectedResult);
109
+
}
110
+
111
+
@ParameterizedTest
112
+
@CsvSource(
113
+
delimiter = ':',
114
+
value = {
115
+
"COSR(0) : 1",
116
+
"COSR(1) : 0.5403023058681398",
117
+
"COSR(19) : 0.9887046181866692",
118
+
"COSR(-19) : 0.9887046181866692"
119
+
})
120
+
void testCosR(String expression, String expectedResult)
121
+
throws EvaluationException, ParseException {
122
+
assertExpressionHasExpectedResult(expression, expectedResult);
123
+
}
124
+
125
+
@ParameterizedTest
126
+
@CsvSource(
127
+
delimiter = ':',
128
+
value = {
129
+
"TANR(0) : 0",
130
+
"TANR(1) : 1.5574077246549023",
131
+
"TANR(19) : 0.15158947061240008",
132
+
"TANR(-19) : -0.15158947061240008"
133
+
})
134
+
void testTanR(String expression, String expectedResult)
135
+
throws EvaluationException, ParseException {
136
+
assertExpressionHasExpectedResult(expression, expectedResult);
137
+
}
138
+
139
+
@ParameterizedTest
140
+
@CsvSource(
141
+
delimiter = ':',
142
+
value = {
143
+
"COT(1) : 57.28996163075943",
144
+
"COT(19) : 2.9042108776758226",
145
+
"COT(-19) : -2.9042108776758226"
146
+
})
147
+
void testCoTan(String expression, String expectedResult)
148
+
throws EvaluationException, ParseException {
149
+
assertExpressionHasExpectedResult(expression, expectedResult);
150
+
}
151
+
152
+
@Test
153
+
void testCoTanThrowsException() {
154
+
Assertions.assertThatThrownBy(() -> new Expression("COT(0)").evaluate())
155
+
.isInstanceOf(EvaluationException.class)
156
+
.hasMessage("Parameter must not be zero");
157
+
}
158
+
159
+
@ParameterizedTest
160
+
@CsvSource(
161
+
delimiter = ':',
162
+
value = {
163
+
"COTH(1) : 1.3130352854993315",
164
+
"COTH(5) : 1.0000908039820193",
165
+
"COTH(-5) : -1.0000908039820193"
166
+
})
167
+
void testCotH(String expression, String expectedResult)
168
+
throws EvaluationException, ParseException {
169
+
assertExpressionHasExpectedResult(expression, expectedResult);
170
+
}
171
+
172
+
@Test
173
+
void testCotHThrowsException() {
174
+
Assertions.assertThatThrownBy(() -> new Expression("COTH(0)").evaluate())
175
+
.isInstanceOf(EvaluationException.class)
176
+
.hasMessage("Parameter must not be zero");
177
+
}
178
+
179
+
@ParameterizedTest
180
+
@CsvSource(
181
+
delimiter = ':',
182
+
value = {
183
+
"COTR(1) : 0.6420926159343306",
184
+
"COTR(19) : 6.596764247280111",
185
+
"COTR(-19) : -6.596764247280111"
186
+
})
187
+
void testCoTanR(String expression, String expectedResult)
188
+
throws EvaluationException, ParseException {
189
+
assertExpressionHasExpectedResult(expression, expectedResult);
190
+
}
191
+
192
+
@Test
193
+
void testCoTanRThrowsException() {
194
+
Assertions.assertThatThrownBy(() -> new Expression("COTR(0)").evaluate())
195
+
.isInstanceOf(EvaluationException.class)
196
+
.hasMessage("Parameter must not be zero");
197
+
}
198
+
199
+
@ParameterizedTest
200
+
@CsvSource(
201
+
delimiter = ':',
202
+
value = {
203
+
"SEC(1) : 1.0001523280439077",
204
+
"SEC(19) : 1.0576206811866706",
205
+
"SEC(-19) : 1.0576206811866706"
206
+
})
207
+
void testSec(String expression, String expectedResult)
208
+
throws EvaluationException, ParseException {
209
+
assertExpressionHasExpectedResult(expression, expectedResult);
210
+
}
211
+
212
+
@Test
213
+
void testSecThrowsException() {
214
+
Assertions.assertThatThrownBy(() -> new Expression("SEC(0)").evaluate())
215
+
.isInstanceOf(EvaluationException.class)
216
+
.hasMessage("Parameter must not be zero");
217
+
}
218
+
219
+
@ParameterizedTest
220
+
@CsvSource(
221
+
delimiter = ':',
222
+
value = {
223
+
"SECH(1) : 0.6480542736638853",
224
+
"SECH(19) : 0.000000011205592875074534",
225
+
"SECH(-19) : 0.000000011205592875074534"
226
+
})
227
+
void testSecH(String expression, String expectedResult)
228
+
throws EvaluationException, ParseException {
229
+
assertExpressionHasExpectedResult(expression, expectedResult);
230
+
}
231
+
232
+
@Test
233
+
void testSecHThrowsException() {
234
+
Assertions.assertThatThrownBy(() -> new Expression("SECH(0)").evaluate())
235
+
.isInstanceOf(EvaluationException.class)
236
+
.hasMessage("Parameter must not be zero");
237
+
}
238
+
239
+
@ParameterizedTest
240
+
@CsvSource(
241
+
delimiter = ':',
242
+
value = {
243
+
"SECR(1) : 1.8508157176809255",
244
+
"SECR(19) : 1.01142442505634",
245
+
"SECR(-19) : 1.01142442505634"
246
+
})
247
+
void testSecR(String expression, String expectedResult)
248
+
throws EvaluationException, ParseException {
249
+
assertExpressionHasExpectedResult(expression, expectedResult);
250
+
}
251
+
252
+
@Test
253
+
void testSecRThrowsException() {
254
+
Assertions.assertThatThrownBy(() -> new Expression("SECR(0)").evaluate())
255
+
.isInstanceOf(EvaluationException.class)
256
+
.hasMessage("Parameter must not be zero");
257
+
}
258
+
259
+
@ParameterizedTest
260
+
@CsvSource(
261
+
delimiter = ':',
262
+
value = {
263
+
"CSC(1) : 57.298688498550185",
264
+
"CSC(19) : 3.0715534867572423",
265
+
"CSC(-19) : -3.0715534867572423"
266
+
})
267
+
void testCSC(String expression, String expectedResult)
268
+
throws EvaluationException, ParseException {
269
+
assertExpressionHasExpectedResult(expression, expectedResult);
270
+
}
271
+
272
+
@Test
273
+
void testCscThrowsException() {
274
+
Assertions.assertThatThrownBy(() -> new Expression("CSC(0)").evaluate())
275
+
.isInstanceOf(EvaluationException.class)
276
+
.hasMessage("Parameter must not be zero");
277
+
}
278
+
279
+
@ParameterizedTest
280
+
@CsvSource(
281
+
delimiter = ':',
282
+
value = {
283
+
"CSCH(1) : 0.8509181282393216",
284
+
"CSCH(19) : 0.000000011205592875074534",
285
+
"CSCH(-19) : -0.000000011205592875074534"
286
+
})
287
+
void testCSCH(String expression, String expectedResult)
288
+
throws EvaluationException, ParseException {
289
+
assertExpressionHasExpectedResult(expression, expectedResult);
290
+
}
291
+
292
+
@Test
293
+
void testCscHThrowsException() {
294
+
Assertions.assertThatThrownBy(() -> new Expression("CSCH(0)").evaluate())
295
+
.isInstanceOf(EvaluationException.class)
296
+
.hasMessage("Parameter must not be zero");
297
+
}
298
+
299
+
@ParameterizedTest
300
+
@CsvSource(
301
+
delimiter = ':',
302
+
value = {
303
+
"CSCR(1) : 1.1883951057781212",
304
+
"CSCR(19) : 6.672128486037505",
305
+
"CSCR(-19) : -6.672128486037505"
306
+
})
307
+
void testCSCR(String expression, String expectedResult)
308
+
throws EvaluationException, ParseException {
309
+
assertExpressionHasExpectedResult(expression, expectedResult);
310
+
}
311
+
312
+
@Test
313
+
void testCscRThrowsException() {
314
+
Assertions.assertThatThrownBy(() -> new Expression("CSCR(0)").evaluate())
315
+
.isInstanceOf(EvaluationException.class)
316
+
.hasMessage("Parameter must not be zero");
317
+
}
318
+
319
+
@ParameterizedTest
320
+
@CsvSource(
321
+
delimiter = ':',
322
+
value = {
323
+
"ACOS(0) : 90",
324
+
"ACOS(1) : 0",
325
+
"ACOS(-1) : 180",
326
+
})
327
+
void testAcos(String expression, String expectedResult)
328
+
throws EvaluationException, ParseException {
329
+
assertExpressionHasExpectedResult(expression, expectedResult);
330
+
}
331
+
332
+
@ParameterizedTest
333
+
@CsvSource(
334
+
delimiter = ':',
335
+
value = {
336
+
"ACOSH(1) : 0",
337
+
"ACOSH(2) : 1.3169578969248166",
338
+
"ACOSH(3) : 1.762747174039086",
339
+
})
340
+
void testAcosH(String expression, String expectedResult)
341
+
throws EvaluationException, ParseException {
342
+
assertExpressionHasExpectedResult(expression, expectedResult);
343
+
}
344
+
345
+
@ParameterizedTest
346
+
@ValueSource(doubles = {-1, -0.5, 0, 0.5, 0.9})
347
+
void testAcosHThrowsException(double d) {
348
+
Assertions.assertThatThrownBy(() -> new Expression("ACOSH(x)").with("x", d).evaluate())
349
+
.isInstanceOf(EvaluationException.class)
350
+
.hasMessage("Value must be greater or equal to one");
351
+
}
352
+
353
+
@ParameterizedTest
354
+
@CsvSource(
355
+
delimiter = ':',
356
+
value = {
357
+
"ACOSR(0) : 1.5707963267948966",
358
+
"ACOSR(1) : 0",
359
+
"ACOSR(-1) : 3.141592653589793",
360
+
})
361
+
void testAcosR(String expression, String expectedResult)
362
+
throws EvaluationException, ParseException {
363
+
assertExpressionHasExpectedResult(expression, expectedResult);
364
+
}
365
+
366
+
@ParameterizedTest
367
+
@CsvSource(
368
+
delimiter = ':',
369
+
value = {
370
+
"ACOT(1) : 45",
371
+
"ACOT(-1) : -45",
372
+
})
373
+
void testAcot(String expression, String expectedResult)
374
+
throws EvaluationException, ParseException {
375
+
assertExpressionHasExpectedResult(expression, expectedResult);
376
+
}
377
+
378
+
@Test
379
+
void testAcotThrowsException() {
380
+
Assertions.assertThatThrownBy(() -> new Expression("ACOT(0)").evaluate())
381
+
.isInstanceOf(EvaluationException.class)
382
+
.hasMessage("Parameter must not be zero");
383
+
}
384
+
385
+
@ParameterizedTest
386
+
@CsvSource(
387
+
delimiter = ':',
388
+
value = {
389
+
"ACOTR(1) : 0.7853981633974483",
390
+
"ACOTR(-1) : -0.7853981633974483",
391
+
})
392
+
void testAcotR(String expression, String expectedResult)
393
+
throws EvaluationException, ParseException {
394
+
assertExpressionHasExpectedResult(expression, expectedResult);
395
+
}
396
+
397
+
@Test
398
+
void testAcotRThrowsException() {
399
+
Assertions.assertThatThrownBy(() -> new Expression("ACOTR(0)").evaluate())
400
+
.isInstanceOf(EvaluationException.class)
401
+
.hasMessage("Parameter must not be zero");
402
+
}
403
+
404
+
@ParameterizedTest
405
+
@CsvSource(
406
+
delimiter = ':',
407
+
value = {
408
+
"ASIN(0) : 0",
409
+
"ASIN(1) : 90",
410
+
"ASIN(-1) : -90",
411
+
})
412
+
void testAsin(String expression, String expectedResult)
413
+
throws EvaluationException, ParseException {
414
+
assertExpressionHasExpectedResult(expression, expectedResult);
415
+
}
416
+
417
+
@ParameterizedTest
418
+
@CsvSource(
419
+
delimiter = ':',
420
+
value = {
421
+
"ASINH(0) : 0",
422
+
"ASINH(1) : 0.8813735870195429",
423
+
"ASINH(-1) : -0.8813735870195428",
424
+
})
425
+
void testAsinH(String expression, String expectedResult)
426
+
throws EvaluationException, ParseException {
427
+
assertExpressionHasExpectedResult(expression, expectedResult);
428
+
}
429
+
430
+
@ParameterizedTest
431
+
@CsvSource(
432
+
delimiter = ':',
433
+
value = {
434
+
"ASINR(0) : 0",
435
+
"ASINR(1) : 1.5707963267948966",
436
+
"ASINR(-1) : -1.5707963267948966",
437
+
})
438
+
void testAsinR(String expression, String expectedResult)
439
+
throws EvaluationException, ParseException {
440
+
assertExpressionHasExpectedResult(expression, expectedResult);
441
+
}
442
+
443
+
@ParameterizedTest
444
+
@CsvSource(
445
+
delimiter = ':',
446
+
value = {
447
+
"ATAN(0) : 0",
448
+
"ATAN(1) : 45",
449
+
"ATAN(-1) : -45",
450
+
})
451
+
void testAtan(String expression, String expectedResult)
452
+
throws EvaluationException, ParseException {
453
+
assertExpressionHasExpectedResult(expression, expectedResult);
454
+
}
455
+
456
+
@ParameterizedTest
457
+
@CsvSource(
458
+
delimiter = ':',
459
+
value = {
460
+
"ATANH(0) : 0",
461
+
"ATANH(0.9) : 1.4722194895832204",
462
+
"ATANH(-0.9) : -1.4722194895832204",
463
+
})
464
+
void testAtanH(String expression, String expectedResult)
465
+
throws EvaluationException, ParseException {
466
+
assertExpressionHasExpectedResult(expression, expectedResult);
467
+
}
468
+
469
+
@ParameterizedTest
470
+
@ValueSource(doubles = {-1.1, -1.0, 1.0, 1.1})
471
+
void testAtanHThrowsException(double d) {
472
+
Assertions.assertThatThrownBy(() -> new Expression("ATANH(x)").with("x", d).evaluate())
473
+
.isInstanceOf(EvaluationException.class)
474
+
.hasMessage("Absolute value must be less than 1");
475
+
}
476
+
477
+
@ParameterizedTest
478
+
@CsvSource(
479
+
delimiter = ':',
480
+
value = {
481
+
"ATANR(0) : 0",
482
+
"ATANR(1) : 0.7853981633974483",
483
+
"ATANR(-1) : -0.7853981633974483",
484
+
})
485
+
void testAtanR(String expression, String expectedResult)
486
+
throws EvaluationException, ParseException {
487
+
assertExpressionHasExpectedResult(expression, expectedResult);
488
+
}
489
+
490
+
@ParameterizedTest
491
+
@CsvSource(
492
+
delimiter = ':',
493
+
value = {
494
+
"ATAN2(0,0) : 0",
495
+
"ATAN2(0,1) : 0",
496
+
"ATAN2(0,-1) : 180",
497
+
"ATAN2(1,0) : 90",
498
+
"ATAN2(1,1) : 45",
499
+
"ATAN2(1,-1) : 135",
500
+
"ATAN2(-1,0) : -90",
501
+
"ATAN2(-1,1) : -45",
502
+
"ATAN2(-1,-1) : -135",
503
+
})
504
+
void testAtan2(String expression, String expectedResult)
505
+
throws EvaluationException, ParseException {
506
+
assertExpressionHasExpectedResult(expression, expectedResult);
507
+
}
508
+
509
+
@ParameterizedTest
510
+
@CsvSource(
511
+
delimiter = ':',
512
+
value = {
513
+
"ATAN2R(0,0) : 0",
514
+
"ATAN2R(0,1) : 0",
515
+
"ATAN2R(0,-1) : 3.141592653589793",
516
+
"ATAN2R(1,0) : 1.5707963267948966",
517
+
"ATAN2R(1,1) : 0.7853981633974483",
518
+
"ATAN2R(1,-1) : 2.356194490192345",
519
+
"ATAN2R(-1,0) : -1.5707963267948966",
520
+
"ATAN2R(-1,1) : -0.7853981633974483",
521
+
"ATAN2R(-1,-1) : -2.356194490192345",
522
+
})
523
+
void testAtan2R(String expression, String expectedResult)
524
+
throws EvaluationException, ParseException {
525
+
assertExpressionHasExpectedResult(expression, expectedResult);
526
+
}
527
+
528
+
@ParameterizedTest
529
+
@CsvSource(
530
+
delimiter = ':',
531
+
value = {
532
+
"COSH(0) : 1",
533
+
"COSH(1) : 1.543080634815244",
534
+
"COSH(-1) : 1.543080634815244",
535
+
})
536
+
void testCosH(String expression, String expectedResult)
537
+
throws EvaluationException, ParseException {
538
+
assertExpressionHasExpectedResult(expression, expectedResult);
539
+
}
540
+
541
+
@ParameterizedTest
542
+
@CsvSource(
543
+
delimiter = ':',
544
+
value = {
545
+
"SINH(0) : 0",
546
+
"SINH(1) : 1.1752011936438014",
547
+
"SINH(-1) : -1.1752011936438014",
548
+
})
549
+
void testSinH(String expression, String expectedResult)
550
+
throws EvaluationException, ParseException {
551
+
assertExpressionHasExpectedResult(expression, expectedResult);
552
+
}
553
+
554
+
@ParameterizedTest
555
+
@CsvSource(
556
+
delimiter = ':',
557
+
value = {
558
+
"TANH(0) : 0",
559
+
"TANH(1) : 0.7615941559557649",
560
+
"TANH(-1) : -0.7615941559557649",
561
+
})
562
+
void testTanH(String expression, String expectedResult)
563
+
throws EvaluationException, ParseException {
564
+
assertExpressionHasExpectedResult(expression, expectedResult);
565
+
}
566
+
}
+93
src/test/java/com/ezylang/evalex/operators/OperatorTest.java
+93
src/test/java/com/ezylang/evalex/operators/OperatorTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators;
17
+
18
+
import static com.ezylang.evalex.operators.OperatorIfc.OPERATOR_PRECEDENCE_MULTIPLICATIVE;
19
+
import static com.ezylang.evalex.operators.OperatorIfc.OPERATOR_PRECEDENCE_UNARY;
20
+
import static org.assertj.core.api.Assertions.assertThat;
21
+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
22
+
23
+
import com.ezylang.evalex.Expression;
24
+
import com.ezylang.evalex.data.EvaluationValue;
25
+
import com.ezylang.evalex.parser.Token;
26
+
import org.junit.jupiter.api.Test;
27
+
28
+
class OperatorTest {
29
+
30
+
@Test
31
+
void testPrefixOperator() {
32
+
OperatorIfc operator = new CorrectPrefixOperator();
33
+
34
+
assertThat(operator.getPrecedence()).isEqualTo(OPERATOR_PRECEDENCE_UNARY);
35
+
assertThat(operator.isLeftAssociative()).isFalse();
36
+
37
+
assertThat(operator.isPrefix()).isTrue();
38
+
assertThat(operator.isPostfix()).isFalse();
39
+
assertThat(operator.isInfix()).isFalse();
40
+
}
41
+
42
+
@Test
43
+
void testPostfixOperator() {
44
+
OperatorIfc operator = new CorrectPostfixOperator();
45
+
46
+
assertThat(operator.getPrecedence()).isEqualTo(88);
47
+
assertThat(operator.isLeftAssociative()).isTrue();
48
+
49
+
assertThat(operator.isPrefix()).isFalse();
50
+
assertThat(operator.isPostfix()).isTrue();
51
+
assertThat(operator.isInfix()).isFalse();
52
+
}
53
+
54
+
@Test
55
+
void testInfixOperator() {
56
+
OperatorIfc operator = new CorrectInfixOperator();
57
+
58
+
assertThat(operator.getPrecedence()).isEqualTo(OPERATOR_PRECEDENCE_MULTIPLICATIVE);
59
+
assertThat(operator.isLeftAssociative()).isTrue();
60
+
61
+
assertThat(operator.isPrefix()).isFalse();
62
+
assertThat(operator.isPostfix()).isFalse();
63
+
assertThat(operator.isInfix()).isTrue();
64
+
}
65
+
66
+
@Test
67
+
void testThrowsFunctionParameterAnnotationNotFoundException() {
68
+
69
+
assertThatThrownBy(DummyAnnotationOperator::new)
70
+
.isInstanceOf(OperatorAnnotationNotFoundException.class)
71
+
.hasMessage(
72
+
"Operator annotation for"
73
+
+ " 'com.ezylang.evalex.operators.OperatorTest$DummyAnnotationOperator' not"
74
+
+ " found");
75
+
}
76
+
77
+
@PrefixOperator(leftAssociative = false)
78
+
private static class CorrectPrefixOperator extends DummyAnnotationOperator {}
79
+
80
+
@PostfixOperator(precedence = 88)
81
+
private static class CorrectPostfixOperator extends DummyAnnotationOperator {}
82
+
83
+
@InfixOperator(precedence = OPERATOR_PRECEDENCE_MULTIPLICATIVE)
84
+
private static class CorrectInfixOperator extends DummyAnnotationOperator {}
85
+
86
+
private static class DummyAnnotationOperator extends AbstractOperator {
87
+
@Override
88
+
public EvaluationValue evaluate(
89
+
Expression expression, Token operatorToken, EvaluationValue... operands) {
90
+
return new EvaluationValue("OK");
91
+
}
92
+
}
93
+
}
+206
src/test/java/com/ezylang/evalex/operators/arithmetic/ArithmeticOperatorsTest.java
+206
src/test/java/com/ezylang/evalex/operators/arithmetic/ArithmeticOperatorsTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.arithmetic;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
19
+
20
+
import com.ezylang.evalex.BaseEvaluationTest;
21
+
import com.ezylang.evalex.EvaluationException;
22
+
import com.ezylang.evalex.parser.ParseException;
23
+
import org.junit.jupiter.api.Test;
24
+
import org.junit.jupiter.params.ParameterizedTest;
25
+
import org.junit.jupiter.params.provider.CsvSource;
26
+
import org.junit.jupiter.params.provider.ValueSource;
27
+
28
+
class ArithmeticOperatorsTest extends BaseEvaluationTest {
29
+
30
+
@ParameterizedTest
31
+
@ValueSource(
32
+
strings = {
33
+
"3/\"string\"",
34
+
"\"string\"/3",
35
+
"3-\"string\"",
36
+
"\"string\"-3",
37
+
"3%\"string\"",
38
+
"\"string\"%3",
39
+
"3*\"string\"",
40
+
"\"string\"*3",
41
+
"3^\"string\"",
42
+
"\"string\"^3",
43
+
"-\"string\"",
44
+
"+\"string\""
45
+
})
46
+
void testUnsupportedDataType(String expression) {
47
+
assertThatThrownBy(() -> assertExpressionHasExpectedResult(expression, "0"))
48
+
.isInstanceOf(EvaluationException.class)
49
+
.hasMessage("Unsupported data types in operation");
50
+
}
51
+
52
+
@ParameterizedTest
53
+
@CsvSource(
54
+
delimiter = ':',
55
+
value = {
56
+
"1/1 : 1",
57
+
"1/2 : 0.5",
58
+
"1/3 : 0.33333333333333333333333333333333333333333333333333333333333333333333",
59
+
"2/8 : 0.25",
60
+
"8/2 : 4",
61
+
"426376/478 : 892"
62
+
})
63
+
void testInfixDivision(String expression, String expectedResult)
64
+
throws EvaluationException, ParseException {
65
+
assertExpressionHasExpectedResult(expression, expectedResult);
66
+
}
67
+
68
+
@Test
69
+
void testInfixDivisionByZero() {
70
+
assertThatThrownBy(() -> assertExpressionHasExpectedResult("3/0", "0"))
71
+
.isInstanceOf(EvaluationException.class)
72
+
.hasMessage("Division by zero");
73
+
}
74
+
75
+
@ParameterizedTest
76
+
@CsvSource(
77
+
delimiter = ':',
78
+
value = {
79
+
"1-1 : 0",
80
+
"1-0 : 1",
81
+
"0-1 : -1",
82
+
"22-22 : 0",
83
+
"2.8-2.4 : 0.4",
84
+
"1-89282 : -89281",
85
+
"1.54321-1.5432 : 0.00001"
86
+
})
87
+
void testInfixMinus(String expression, String expectedResult)
88
+
throws EvaluationException, ParseException {
89
+
assertExpressionHasExpectedResult(expression, expectedResult);
90
+
}
91
+
92
+
@ParameterizedTest
93
+
@CsvSource(
94
+
delimiter = ':',
95
+
value = {
96
+
"1%1 : 0",
97
+
"0%1 : 0",
98
+
"22%2 : 0",
99
+
"2.8%2 : 0.8",
100
+
"1%89282 : 1",
101
+
"154321%7 : 6",
102
+
"848484%7373 : 589"
103
+
})
104
+
void testInfixModulo(String expression, String expectedResult)
105
+
throws EvaluationException, ParseException {
106
+
assertExpressionHasExpectedResult(expression, expectedResult);
107
+
}
108
+
109
+
@Test
110
+
void testInfixModuloByZero() {
111
+
assertThatThrownBy(() -> assertExpressionHasExpectedResult("3%0", "0"))
112
+
.isInstanceOf(EvaluationException.class)
113
+
.hasMessage("Division by zero");
114
+
}
115
+
116
+
@ParameterizedTest
117
+
@CsvSource(
118
+
delimiter = ':',
119
+
value = {
120
+
"0*0 : 0",
121
+
"0*1 : 0",
122
+
"1*0 : 0",
123
+
"1*1 : 1",
124
+
"2*2 : 4",
125
+
"17*76 : 1292",
126
+
"1.5*2 : 3",
127
+
"2.125*4 : 8.5"
128
+
})
129
+
void testInfixMultiplication(String expression, String expectedResult)
130
+
throws EvaluationException, ParseException {
131
+
assertExpressionHasExpectedResult(expression, expectedResult);
132
+
}
133
+
134
+
@ParameterizedTest
135
+
@CsvSource(
136
+
delimiter = ':',
137
+
value = {
138
+
"0+0 : 0",
139
+
"0+1 : 1",
140
+
"1+0 : 1",
141
+
"1+1 : 2",
142
+
"2+3 : 5",
143
+
"17+76 : 93",
144
+
"1.5+2 : 3.5",
145
+
"2.125+4.125 : 6.25"
146
+
})
147
+
void testInfixPlusNumbers(String expression, String expectedResult)
148
+
throws EvaluationException, ParseException {
149
+
assertExpressionHasExpectedResult(expression, expectedResult);
150
+
}
151
+
152
+
@ParameterizedTest
153
+
@CsvSource(
154
+
delimiter = ':',
155
+
value = {
156
+
"\"hello\"+\" world\" : hello world",
157
+
"77+\"hello\" : 77hello",
158
+
"\"hello\"+77 : hello77",
159
+
"\"1\"+\"1\" : 11",
160
+
"1+\"1\" : 11",
161
+
"\"1\"+1 : 11"
162
+
})
163
+
void testInfixPlusStrings(String expression, String expectedResult)
164
+
throws EvaluationException, ParseException {
165
+
assertExpressionHasExpectedResult(expression, expectedResult);
166
+
}
167
+
168
+
@ParameterizedTest
169
+
@CsvSource(
170
+
delimiter = ':',
171
+
value = {
172
+
"0^0 : 1",
173
+
"0^2 : 0",
174
+
"2^0 : 1",
175
+
"1^1 : 1",
176
+
"1^2 : 1",
177
+
"2^2 : 4",
178
+
"4^4 : 256",
179
+
"2.3^2.2 : 6.248866394748043436",
180
+
"2^-3 : 0.125",
181
+
"-2^-3 : -0.125",
182
+
"-2^-3 : -0.125"
183
+
})
184
+
void testInfixPower(String expression, String expectedResult)
185
+
throws EvaluationException, ParseException {
186
+
assertExpressionHasExpectedResult(expression, expectedResult);
187
+
}
188
+
189
+
@ParameterizedTest
190
+
@CsvSource(
191
+
delimiter = ':',
192
+
value = {"-0 : 0", "-1 : -1", "-2.5 : -2.5", "-987.654 : -987.654"})
193
+
void testPrefixMinus(String expression, String expectedResult)
194
+
throws EvaluationException, ParseException {
195
+
assertExpressionHasExpectedResult(expression, expectedResult);
196
+
}
197
+
198
+
@ParameterizedTest
199
+
@CsvSource(
200
+
delimiter = ':',
201
+
value = {"+0 : 0", "+1 : 1", "+2.5 : 2.5", "+987.654 : 987.654"})
202
+
void testPrefixPlus(String expression, String expectedResult)
203
+
throws EvaluationException, ParseException {
204
+
assertExpressionHasExpectedResult(expression, expectedResult);
205
+
}
206
+
}
+44
src/test/java/com/ezylang/evalex/operators/booleans/InfixAndOperatorTest.java
+44
src/test/java/com/ezylang/evalex/operators/booleans/InfixAndOperatorTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.booleans;
17
+
18
+
import com.ezylang.evalex.BaseEvaluationTest;
19
+
import com.ezylang.evalex.EvaluationException;
20
+
import com.ezylang.evalex.parser.ParseException;
21
+
import org.junit.jupiter.params.ParameterizedTest;
22
+
import org.junit.jupiter.params.provider.CsvSource;
23
+
24
+
class InfixAndOperatorTest extends BaseEvaluationTest {
25
+
26
+
@ParameterizedTest
27
+
@CsvSource(
28
+
delimiter = ':',
29
+
value = {
30
+
"1&&1 : true",
31
+
"1&&2 : true",
32
+
"0&&1 : false",
33
+
"22&&33 : true",
34
+
"\"true\"&&\"true\" : true",
35
+
"\"true\"&&\"false\" : false",
36
+
"\"false\"&&\"false\" : false",
37
+
"(1==1)&&(2==2) : true",
38
+
"(5>4)&&(4<6) :true"
39
+
})
40
+
void testInfixLessLiterals(String expression, String expectedResult)
41
+
throws EvaluationException, ParseException {
42
+
assertExpressionHasExpectedResult(expression, expectedResult);
43
+
}
44
+
}
+132
src/test/java/com/ezylang/evalex/operators/booleans/InfixEqualsOperatorTest.java
+132
src/test/java/com/ezylang/evalex/operators/booleans/InfixEqualsOperatorTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.booleans;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import com.ezylang.evalex.BaseEvaluationTest;
21
+
import com.ezylang.evalex.EvaluationException;
22
+
import com.ezylang.evalex.Expression;
23
+
import com.ezylang.evalex.parser.ParseException;
24
+
import java.math.BigDecimal;
25
+
import java.util.Arrays;
26
+
import java.util.HashMap;
27
+
import java.util.Map;
28
+
import org.junit.jupiter.api.Test;
29
+
import org.junit.jupiter.params.ParameterizedTest;
30
+
import org.junit.jupiter.params.provider.CsvSource;
31
+
32
+
class InfixEqualsOperatorTest extends BaseEvaluationTest {
33
+
34
+
@ParameterizedTest
35
+
@CsvSource(
36
+
delimiter = ':',
37
+
value = {
38
+
"1=1 : true",
39
+
"1==1 : true",
40
+
"0=0 : true",
41
+
"1=0 : false",
42
+
"0=1 : false",
43
+
"21.678=21.678 : true",
44
+
"\"abc\"=\"abc\" : true",
45
+
"\"abc\"=\"xyz\" : false",
46
+
"1+2=4-1 : true",
47
+
"-5.2=-5.2 :true"
48
+
})
49
+
void testInfixEqualsLiterals(String expression, String expectedResult)
50
+
throws EvaluationException, ParseException {
51
+
assertExpressionHasExpectedResult(expression, expectedResult);
52
+
}
53
+
54
+
@Test
55
+
void testInfixEqualsVariables() throws EvaluationException, ParseException {
56
+
Expression expression = new Expression("a=b");
57
+
58
+
assertThat(
59
+
expression
60
+
.with("a", new BigDecimal("1.4"))
61
+
.and("b", new BigDecimal("1.4"))
62
+
.evaluate()
63
+
.getBooleanValue())
64
+
.isTrue();
65
+
66
+
assertThat(expression.with("a", "Hello").and("b", "Hello").evaluate().getBooleanValue())
67
+
.isTrue();
68
+
69
+
assertThat(expression.with("a", "Hello").and("b", "Goodbye").evaluate().getBooleanValue())
70
+
.isFalse();
71
+
72
+
assertThat(expression.with("a", true).and("b", true).evaluate().getBooleanValue()).isTrue();
73
+
74
+
assertThat(expression.with("a", false).and("b", true).evaluate().getBooleanValue()).isFalse();
75
+
}
76
+
77
+
@Test
78
+
void testInfixEqualsArrays() throws EvaluationException, ParseException {
79
+
Expression expression = new Expression("a=b");
80
+
81
+
assertThat(
82
+
expression
83
+
.with("a", Arrays.asList("a", "b", "c"))
84
+
.and("b", Arrays.asList("a", "b", "c"))
85
+
.evaluate()
86
+
.getBooleanValue())
87
+
.isTrue();
88
+
89
+
assertThat(
90
+
expression
91
+
.with("a", Arrays.asList("a", "b", "c"))
92
+
.and("b", Arrays.asList("c", "b", "a"))
93
+
.evaluate()
94
+
.getBooleanValue())
95
+
.isFalse();
96
+
}
97
+
98
+
@Test
99
+
void testInfixEqualsStructures() throws EvaluationException, ParseException {
100
+
Expression expression = new Expression("a=b");
101
+
102
+
Map<String, BigDecimal> structure1 =
103
+
new HashMap<>() {
104
+
{
105
+
put("a", new BigDecimal(35));
106
+
put("b", new BigDecimal(99));
107
+
}
108
+
};
109
+
110
+
Map<String, BigDecimal> structure2 =
111
+
new HashMap<>() {
112
+
{
113
+
put("a", new BigDecimal(35));
114
+
put("b", new BigDecimal(99));
115
+
}
116
+
};
117
+
118
+
Map<String, BigDecimal> structure3 =
119
+
new HashMap<>() {
120
+
{
121
+
put("a", new BigDecimal(45));
122
+
put("b", new BigDecimal(99));
123
+
}
124
+
};
125
+
126
+
assertThat(expression.with("a", structure1).and("b", structure2).evaluate().getBooleanValue())
127
+
.isTrue();
128
+
129
+
assertThat(expression.with("a", structure1).and("b", structure3).evaluate().getBooleanValue())
130
+
.isFalse();
131
+
}
132
+
}
+49
src/test/java/com/ezylang/evalex/operators/booleans/InfixGreaterEqualsOperatorTest.java
+49
src/test/java/com/ezylang/evalex/operators/booleans/InfixGreaterEqualsOperatorTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.booleans;
17
+
18
+
import com.ezylang.evalex.BaseEvaluationTest;
19
+
import com.ezylang.evalex.EvaluationException;
20
+
import com.ezylang.evalex.parser.ParseException;
21
+
import org.junit.jupiter.params.ParameterizedTest;
22
+
import org.junit.jupiter.params.provider.CsvSource;
23
+
24
+
class InfixGreaterEqualsOperatorTest extends BaseEvaluationTest {
25
+
26
+
@ParameterizedTest
27
+
@CsvSource(
28
+
delimiter = ':',
29
+
value = {
30
+
"1>=1 : true",
31
+
"2>=1 : true",
32
+
"2.1>=2.0 : true",
33
+
"2.0>=2.0 : true",
34
+
"21.677>=21.678 : false",
35
+
"21.679>=21.678 : true",
36
+
"\"abc\">=\"abc\" : true",
37
+
"\"abd\">=\"abc\" : true",
38
+
"\"abc\">=\"xyz\" : false",
39
+
"\"ABC\">=\"abc\" : false",
40
+
"\"9\">=\"5\" : true",
41
+
"\"9\">=\"9\" : true",
42
+
"-4>=-4 :true",
43
+
"-4>=-5 :true"
44
+
})
45
+
void testInfixGreaterEqualsLiterals(String expression, String expectedResult)
46
+
throws EvaluationException, ParseException {
47
+
assertExpressionHasExpectedResult(expression, expectedResult);
48
+
}
49
+
}
+45
src/test/java/com/ezylang/evalex/operators/booleans/InfixGreaterOperatorTest.java
+45
src/test/java/com/ezylang/evalex/operators/booleans/InfixGreaterOperatorTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.booleans;
17
+
18
+
import com.ezylang.evalex.BaseEvaluationTest;
19
+
import com.ezylang.evalex.EvaluationException;
20
+
import com.ezylang.evalex.parser.ParseException;
21
+
import org.junit.jupiter.params.ParameterizedTest;
22
+
import org.junit.jupiter.params.provider.CsvSource;
23
+
24
+
class InfixGreaterOperatorTest extends BaseEvaluationTest {
25
+
26
+
@ParameterizedTest
27
+
@CsvSource(
28
+
delimiter = ':',
29
+
value = {
30
+
"1>1 : false",
31
+
"2>1 : true",
32
+
"2.1>2.0 : true",
33
+
"2.0>2.0 : false",
34
+
"21.679>21.678 : true",
35
+
"\"abd\">\"abc\" : true",
36
+
"\"abc\">\"xyz\" : false",
37
+
"\"ABC\">\"abc\" : false",
38
+
"\"9\">\"5\" : true",
39
+
"-4>-5 :true"
40
+
})
41
+
void testInfixGreaterLiterals(String expression, String expectedResult)
42
+
throws EvaluationException, ParseException {
43
+
assertExpressionHasExpectedResult(expression, expectedResult);
44
+
}
45
+
}
+49
src/test/java/com/ezylang/evalex/operators/booleans/InfixLessEqualsOperatorTest.java
+49
src/test/java/com/ezylang/evalex/operators/booleans/InfixLessEqualsOperatorTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.booleans;
17
+
18
+
import com.ezylang.evalex.BaseEvaluationTest;
19
+
import com.ezylang.evalex.EvaluationException;
20
+
import com.ezylang.evalex.parser.ParseException;
21
+
import org.junit.jupiter.params.ParameterizedTest;
22
+
import org.junit.jupiter.params.provider.CsvSource;
23
+
24
+
class InfixLessEqualsOperatorTest extends BaseEvaluationTest {
25
+
26
+
@ParameterizedTest
27
+
@CsvSource(
28
+
delimiter = ':',
29
+
value = {
30
+
"1<=1 : true",
31
+
"1<=2 : true",
32
+
"2.0<=2.1 : true",
33
+
"2.0<=2.0 : true",
34
+
"21.678<=21.677 : false",
35
+
"21.678<=21.679 : true",
36
+
"\"abc\"<=\"abc\" : true",
37
+
"\"abc\"<=\"abd\" : true",
38
+
"\"xyz\"<=\"abc\" : false",
39
+
"\"abc\"<=\"ABC\" : false",
40
+
"\"5\"<=\"9\" : true",
41
+
"\"9\"<=\"9\" : true",
42
+
"-4<=-4 :true",
43
+
"-5<=-4 :true"
44
+
})
45
+
void testInfixLessEqualsLiterals(String expression, String expectedResult)
46
+
throws EvaluationException, ParseException {
47
+
assertExpressionHasExpectedResult(expression, expectedResult);
48
+
}
49
+
}
+47
src/test/java/com/ezylang/evalex/operators/booleans/InfixLessOperatorTest.java
+47
src/test/java/com/ezylang/evalex/operators/booleans/InfixLessOperatorTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.booleans;
17
+
18
+
import com.ezylang.evalex.BaseEvaluationTest;
19
+
import com.ezylang.evalex.EvaluationException;
20
+
import com.ezylang.evalex.parser.ParseException;
21
+
import org.junit.jupiter.params.ParameterizedTest;
22
+
import org.junit.jupiter.params.provider.CsvSource;
23
+
24
+
class InfixLessOperatorTest extends BaseEvaluationTest {
25
+
26
+
@ParameterizedTest
27
+
@CsvSource(
28
+
delimiter = ':',
29
+
value = {
30
+
"1<1 : false",
31
+
"2<1 : false",
32
+
"1<2 : true",
33
+
"2.1<2.0 : false",
34
+
"2.0<2.1 : true",
35
+
"2.0<2.0 : false",
36
+
"21.678<21.679 : true",
37
+
"\"abc\"<\"abd\" : true",
38
+
"\"abc\"<\"xyz\" : true",
39
+
"\"abc\"<\"ABC\" : false",
40
+
"\"5\"<\"9\" : true",
41
+
"-5<-4 :true"
42
+
})
43
+
void testInfixLessLiterals(String expression, String expectedResult)
44
+
throws EvaluationException, ParseException {
45
+
assertExpressionHasExpectedResult(expression, expectedResult);
46
+
}
47
+
}
+132
src/test/java/com/ezylang/evalex/operators/booleans/InfixNotEqualsOperatorTest.java
+132
src/test/java/com/ezylang/evalex/operators/booleans/InfixNotEqualsOperatorTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.booleans;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import com.ezylang.evalex.BaseEvaluationTest;
21
+
import com.ezylang.evalex.EvaluationException;
22
+
import com.ezylang.evalex.Expression;
23
+
import com.ezylang.evalex.parser.ParseException;
24
+
import java.math.BigDecimal;
25
+
import java.util.Arrays;
26
+
import java.util.HashMap;
27
+
import java.util.Map;
28
+
import org.junit.jupiter.api.Test;
29
+
import org.junit.jupiter.params.ParameterizedTest;
30
+
import org.junit.jupiter.params.provider.CsvSource;
31
+
32
+
class InfixNotEqualsOperatorTest extends BaseEvaluationTest {
33
+
34
+
@ParameterizedTest
35
+
@CsvSource(
36
+
delimiter = ':',
37
+
value = {
38
+
"1!=1 : false",
39
+
"1<>1 : false",
40
+
"0!=0 : false",
41
+
"1!=0 : true",
42
+
"0!=1 : true",
43
+
"21.678!=21.678 : false",
44
+
"\"abc\"!=\"abc\" : false",
45
+
"\"abc\"!=\"xyz\" : true",
46
+
"1+2!=4-1 : false",
47
+
"-5.2!=-5.2 :false"
48
+
})
49
+
void testInfixNotEqualsLiterals(String expression, String expectedResult)
50
+
throws EvaluationException, ParseException {
51
+
assertExpressionHasExpectedResult(expression, expectedResult);
52
+
}
53
+
54
+
@Test
55
+
void testInfixNotEqualsVariables() throws EvaluationException, ParseException {
56
+
Expression expression = new Expression("a!=b");
57
+
58
+
assertThat(
59
+
expression
60
+
.with("a", new BigDecimal("1.4"))
61
+
.and("b", new BigDecimal("1.4"))
62
+
.evaluate()
63
+
.getBooleanValue())
64
+
.isFalse();
65
+
66
+
assertThat(expression.with("a", "Hello").and("b", "Hello").evaluate().getBooleanValue())
67
+
.isFalse();
68
+
69
+
assertThat(expression.with("a", "Hello").and("b", "Goodbye").evaluate().getBooleanValue())
70
+
.isTrue();
71
+
72
+
assertThat(expression.with("a", true).and("b", true).evaluate().getBooleanValue()).isFalse();
73
+
74
+
assertThat(expression.with("a", false).and("b", true).evaluate().getBooleanValue()).isTrue();
75
+
}
76
+
77
+
@Test
78
+
void testInfixNotEqualsArrays() throws EvaluationException, ParseException {
79
+
Expression expression = new Expression("a!=b");
80
+
81
+
assertThat(
82
+
expression
83
+
.with("a", Arrays.asList("a", "b", "c"))
84
+
.and("b", Arrays.asList("a", "b", "c"))
85
+
.evaluate()
86
+
.getBooleanValue())
87
+
.isFalse();
88
+
89
+
assertThat(
90
+
expression
91
+
.with("a", Arrays.asList("a", "b", "c"))
92
+
.and("b", Arrays.asList("c", "b", "a"))
93
+
.evaluate()
94
+
.getBooleanValue())
95
+
.isTrue();
96
+
}
97
+
98
+
@Test
99
+
void testInfixNotEqualsStructures() throws EvaluationException, ParseException {
100
+
Expression expression = new Expression("a!=b");
101
+
102
+
Map<String, BigDecimal> structure1 =
103
+
new HashMap<>() {
104
+
{
105
+
put("a", new BigDecimal(35));
106
+
put("b", new BigDecimal(99));
107
+
}
108
+
};
109
+
110
+
Map<String, BigDecimal> structure2 =
111
+
new HashMap<>() {
112
+
{
113
+
put("a", new BigDecimal(35));
114
+
put("b", new BigDecimal(99));
115
+
}
116
+
};
117
+
118
+
Map<String, BigDecimal> structure3 =
119
+
new HashMap<>() {
120
+
{
121
+
put("a", new BigDecimal(45));
122
+
put("b", new BigDecimal(99));
123
+
}
124
+
};
125
+
126
+
assertThat(expression.with("a", structure1).and("b", structure2).evaluate().getBooleanValue())
127
+
.isFalse();
128
+
129
+
assertThat(expression.with("a", structure1).and("b", structure3).evaluate().getBooleanValue())
130
+
.isTrue();
131
+
}
132
+
}
+45
src/test/java/com/ezylang/evalex/operators/booleans/InfixOrOperatorTest.java
+45
src/test/java/com/ezylang/evalex/operators/booleans/InfixOrOperatorTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.booleans;
17
+
18
+
import com.ezylang.evalex.BaseEvaluationTest;
19
+
import com.ezylang.evalex.EvaluationException;
20
+
import com.ezylang.evalex.parser.ParseException;
21
+
import org.junit.jupiter.params.ParameterizedTest;
22
+
import org.junit.jupiter.params.provider.CsvSource;
23
+
24
+
class InfixOrOperatorTest extends BaseEvaluationTest {
25
+
26
+
@ParameterizedTest
27
+
@CsvSource(
28
+
delimiter = ':',
29
+
value = {
30
+
"1||1 : true",
31
+
"1||2 : true",
32
+
"0||1 : true",
33
+
"0||0 : false",
34
+
"22||33 : true",
35
+
"\"true\"||\"true\" : true",
36
+
"\"true\"||\"false\" : true",
37
+
"\"false\"||\"false\" : false",
38
+
"(1==1)||(2==3) : true",
39
+
"(2>4)||(4<6) :true"
40
+
})
41
+
void testInfixLessLiterals(String expression, String expectedResult)
42
+
throws EvaluationException, ParseException {
43
+
assertExpressionHasExpectedResult(expression, expectedResult);
44
+
}
45
+
}
+43
src/test/java/com/ezylang/evalex/operators/booleans/PrefixNotOperatorTest.java
+43
src/test/java/com/ezylang/evalex/operators/booleans/PrefixNotOperatorTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.operators.booleans;
17
+
18
+
import com.ezylang.evalex.BaseEvaluationTest;
19
+
import com.ezylang.evalex.EvaluationException;
20
+
import com.ezylang.evalex.parser.ParseException;
21
+
import org.junit.jupiter.params.ParameterizedTest;
22
+
import org.junit.jupiter.params.provider.CsvSource;
23
+
24
+
class PrefixNotOperatorTest extends BaseEvaluationTest {
25
+
26
+
@ParameterizedTest
27
+
@CsvSource(
28
+
delimiter = ':',
29
+
value = {
30
+
"!0 : true",
31
+
"!1 : false",
32
+
"!2.6 : false",
33
+
"!-2 : false",
34
+
"!\"true\" : false",
35
+
"!\"false\" : true",
36
+
"!(1==1) : false",
37
+
"!(2==3) : true"
38
+
})
39
+
void testInfixLessLiterals(String expression, String expectedResult)
40
+
throws EvaluationException, ParseException {
41
+
assertExpressionHasExpectedResult(expression, expectedResult);
42
+
}
43
+
}
+67
src/test/java/com/ezylang/evalex/parser/ASTNodeTest.java
+67
src/test/java/com/ezylang/evalex/parser/ASTNodeTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import com.ezylang.evalex.functions.basic.MinFunction;
21
+
import com.ezylang.evalex.operators.arithmetic.InfixPlusOperator;
22
+
import com.ezylang.evalex.operators.arithmetic.PrefixMinusOperator;
23
+
import com.ezylang.evalex.parser.Token.TokenType;
24
+
import org.junit.jupiter.api.Test;
25
+
26
+
class ASTNodeTest {
27
+
final Token variable = new Token(1, "variable", TokenType.VARIABLE_OR_CONSTANT);
28
+
29
+
@Test
30
+
void testJSONSingle() {
31
+
ASTNode node = new ASTNode(variable);
32
+
33
+
assertThat(node.toJSON())
34
+
.isEqualTo("{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"variable\"}");
35
+
}
36
+
37
+
@Test
38
+
void testJSONPrefix() {
39
+
Token token = new Token(1, "-", TokenType.PREFIX_OPERATOR, new PrefixMinusOperator());
40
+
ASTNode node = new ASTNode(token, new ASTNode(variable));
41
+
42
+
assertThat(node.toJSON())
43
+
.isEqualTo(
44
+
"{\"type\":\"PREFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"variable\"}]}");
45
+
}
46
+
47
+
@Test
48
+
void testJSONInfix() {
49
+
Token token = new Token(1, "+", TokenType.INFIX_OPERATOR, new InfixPlusOperator());
50
+
ASTNode node = new ASTNode(token, new ASTNode(variable), new ASTNode(variable));
51
+
52
+
assertThat(node.toJSON())
53
+
.isEqualTo(
54
+
"{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"variable\"},{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"variable\"}]}");
55
+
}
56
+
57
+
@Test
58
+
void testJSONFunction() {
59
+
Token token = new Token(1, "+", TokenType.FUNCTION, new MinFunction());
60
+
ASTNode node =
61
+
new ASTNode(token, new ASTNode(variable), new ASTNode(variable), new ASTNode(variable));
62
+
63
+
assertThat(node.toJSON())
64
+
.isEqualTo(
65
+
"{\"type\":\"FUNCTION\",\"value\":\"+\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"variable\"},{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"variable\"},{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"variable\"}]}");
66
+
}
67
+
}
+48
src/test/java/com/ezylang/evalex/parser/BaseParserTest.java
+48
src/test/java/com/ezylang/evalex/parser/BaseParserTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import com.ezylang.evalex.config.ExpressionConfiguration;
21
+
import com.ezylang.evalex.config.TestConfigurationProvider;
22
+
import java.util.List;
23
+
24
+
/** */
25
+
public abstract class BaseParserTest {
26
+
27
+
ExpressionConfiguration configuration =
28
+
TestConfigurationProvider.StandardConfigurationWithAdditionalTestOperators;
29
+
30
+
void assertAllTokensParsedCorrectly(String input, Token... expectedTokens) throws ParseException {
31
+
List<Token> tokensParsed = new Tokenizer(input, configuration).parse();
32
+
33
+
assertThat(tokensParsed).containsExactly(expectedTokens);
34
+
}
35
+
36
+
/**
37
+
* Compares if the generated abstract syntax tree is correct. To visualize the generated JSON in a
38
+
* tree format, i.e. to display the AST, you can use the following online service:<br>
39
+
* <a href="https://vanya.jp.net/vtree/">Online JSON to Tree Diagram Converter</a>
40
+
*/
41
+
void assertASTTreeIsEqualTo(String expression, String treeJSON) throws ParseException {
42
+
43
+
List<Token> tokensParsed = new Tokenizer(expression, configuration).parse();
44
+
ASTNode root =
45
+
new ShuntingYardConverter(expression, tokensParsed, configuration).toAbstractSyntaxTree();
46
+
assertThat(root.toJSON()).isEqualTo(treeJSON);
47
+
}
48
+
}
+33
src/test/java/com/ezylang/evalex/parser/ParseExceptionTest.java
+33
src/test/java/com/ezylang/evalex/parser/ParseExceptionTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import org.junit.jupiter.api.Test;
21
+
22
+
class ParseExceptionTest {
23
+
24
+
@Test
25
+
void testToString() {
26
+
ParseException exception = new ParseException(2, 4, "test", "message");
27
+
28
+
assertThat(exception)
29
+
.hasToString(
30
+
"ParseException(super=BaseException(startPosition=2, endPosition=4, tokenString=test,"
31
+
+ " message=message))");
32
+
}
33
+
}
+49
src/test/java/com/ezylang/evalex/parser/ShuntingYardArrayTest.java
+49
src/test/java/com/ezylang/evalex/parser/ShuntingYardArrayTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import org.junit.jupiter.api.Test;
19
+
20
+
class ShuntingYardArrayTest extends BaseParserTest {
21
+
22
+
@Test
23
+
void testSimpleArray() throws ParseException {
24
+
assertASTTreeIsEqualTo(
25
+
"a[0]",
26
+
"{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"a\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"0\"}]}");
27
+
}
28
+
29
+
@Test
30
+
void testArrayExpression() throws ParseException {
31
+
assertASTTreeIsEqualTo(
32
+
"a[x+y]",
33
+
"{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"a\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"x\"},{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"y\"}]}]}");
34
+
}
35
+
36
+
@Test
37
+
void testArrayNested() throws ParseException {
38
+
assertASTTreeIsEqualTo(
39
+
"a[b[1]]",
40
+
"{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"a\"},{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"b\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"}]}]}");
41
+
}
42
+
43
+
@Test
44
+
void testComplex() throws ParseException {
45
+
assertASTTreeIsEqualTo(
46
+
"a[b[100*(a+b)]-c[2+d[x+y]]]",
47
+
"{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"a\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"b\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"*\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"100\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"a\"},{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"b\"}]}]}]},{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"c\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"},{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"d\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"x\"},{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"y\"}]}]}]}]}]}]}");
48
+
}
49
+
}
+80
src/test/java/com/ezylang/evalex/parser/ShuntingYardConverterTest.java
+80
src/test/java/com/ezylang/evalex/parser/ShuntingYardConverterTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import org.junit.jupiter.api.Test;
19
+
20
+
class ShuntingYardConverterTest extends BaseParserTest {
21
+
22
+
@Test
23
+
void testSingleNumber() throws ParseException {
24
+
assertASTTreeIsEqualTo("1", "{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"}");
25
+
}
26
+
27
+
@Test
28
+
void testSingleVariable() throws ParseException {
29
+
assertASTTreeIsEqualTo("a", "{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"a\"}");
30
+
}
31
+
32
+
@Test
33
+
void testPrefix() throws ParseException {
34
+
assertASTTreeIsEqualTo(
35
+
"-1",
36
+
"{\"type\":\"PREFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"}]}");
37
+
}
38
+
39
+
@Test
40
+
void testPostfix() throws ParseException {
41
+
assertASTTreeIsEqualTo(
42
+
"1?",
43
+
"{\"type\":\"POSTFIX_OPERATOR\",\"value\":\"?\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"}]}");
44
+
}
45
+
46
+
@Test
47
+
void testPrefixPostfix() throws ParseException {
48
+
assertASTTreeIsEqualTo(
49
+
"-1?",
50
+
"{\"type\":\"PREFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"POSTFIX_OPERATOR\",\"value\":\"?\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"}]}]}");
51
+
}
52
+
53
+
@Test
54
+
void testSequential() throws ParseException {
55
+
assertASTTreeIsEqualTo(
56
+
"1+2+3-3-2-1",
57
+
"{\"type\":\"INFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"INFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"INFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"}]},{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\"}]},{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\"}]},{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"}]},{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"}]}");
58
+
}
59
+
60
+
@Test
61
+
void testPrecedence() throws ParseException {
62
+
assertASTTreeIsEqualTo(
63
+
"1+2*3-3^2-1/4",
64
+
"{\"type\":\"INFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"INFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"*\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\"}]}]},{\"type\":\"INFIX_OPERATOR\",\"value\":\"^\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"}]}]},{\"type\":\"INFIX_OPERATOR\",\"value\":\"/\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"4\"}]}]}");
65
+
}
66
+
67
+
@Test
68
+
void testBraces() throws ParseException {
69
+
assertASTTreeIsEqualTo(
70
+
"2*(1/(2+3))",
71
+
"{\"type\":\"INFIX_OPERATOR\",\"value\":\"*\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"/\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\"}]}]}]}");
72
+
}
73
+
74
+
@Test
75
+
void testFunctions() throws ParseException {
76
+
assertASTTreeIsEqualTo(
77
+
"MAX(1,2,3)-MIN(3,2,SUM(1,2,3))",
78
+
"{\"type\":\"INFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"FUNCTION\",\"value\":\"MAX\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\"}]},{\"type\":\"FUNCTION\",\"value\":\"MIN\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"},{\"type\":\"FUNCTION\",\"value\":\"SUM\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\"}]}]}]}");
79
+
}
80
+
}
+136
src/test/java/com/ezylang/evalex/parser/ShuntingYardExceptionsTest.java
+136
src/test/java/com/ezylang/evalex/parser/ShuntingYardExceptionsTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import static com.ezylang.evalex.parser.Token.TokenType.FUNCTION_PARAM_START;
19
+
import static com.ezylang.evalex.parser.Token.TokenType.INFIX_OPERATOR;
20
+
import static com.ezylang.evalex.parser.Token.TokenType.POSTFIX_OPERATOR;
21
+
import static com.ezylang.evalex.parser.Token.TokenType.PREFIX_OPERATOR;
22
+
import static com.ezylang.evalex.parser.Token.TokenType.STRUCTURE_SEPARATOR;
23
+
import static com.ezylang.evalex.parser.Token.TokenType.VARIABLE_OR_CONSTANT;
24
+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
25
+
26
+
import com.ezylang.evalex.Expression;
27
+
import com.ezylang.evalex.operators.arithmetic.InfixMultiplicationOperator;
28
+
import com.ezylang.evalex.operators.arithmetic.PrefixMinusOperator;
29
+
import java.util.Arrays;
30
+
import java.util.List;
31
+
import org.junit.jupiter.api.Test;
32
+
33
+
class ShuntingYardExceptionsTest extends BaseParserTest {
34
+
35
+
@Test
36
+
void testUnexpectedToken() {
37
+
List<Token> tokens = List.of(new Token(1, "x", FUNCTION_PARAM_START));
38
+
assertThatThrownBy(new ShuntingYardConverter("x", tokens, configuration)::toAbstractSyntaxTree)
39
+
.isInstanceOf(ParseException.class)
40
+
.hasMessage("Unexpected token of type 'FUNCTION_PARAM_START'");
41
+
}
42
+
43
+
@Test
44
+
void testMissingPrefixOperand() {
45
+
List<Token> tokens = List.of(new Token(1, "-", PREFIX_OPERATOR, new PrefixMinusOperator()));
46
+
assertThatThrownBy(new ShuntingYardConverter("-", tokens, configuration)::toAbstractSyntaxTree)
47
+
.isInstanceOf(ParseException.class)
48
+
.hasMessage("Missing operand for operator");
49
+
}
50
+
51
+
@Test
52
+
void testMissingSecondInfixOperand() {
53
+
List<Token> tokens =
54
+
Arrays.asList(
55
+
new Token(1, "2", VARIABLE_OR_CONSTANT),
56
+
new Token(2, "*", INFIX_OPERATOR, new InfixMultiplicationOperator()));
57
+
assertThatThrownBy(new ShuntingYardConverter("2*", tokens, configuration)::toAbstractSyntaxTree)
58
+
.isInstanceOf(ParseException.class)
59
+
.hasMessage("Missing second operand for operator");
60
+
}
61
+
62
+
@Test
63
+
void testEmptyExpression() {
64
+
Expression expression = new Expression("");
65
+
66
+
assertThatThrownBy(expression::evaluate)
67
+
.isInstanceOf(ParseException.class)
68
+
.hasMessage("Empty expression");
69
+
}
70
+
71
+
@Test
72
+
void testEmptyExpressionBraces() {
73
+
Expression expression = new Expression("()");
74
+
75
+
assertThatThrownBy(expression::evaluate)
76
+
.isInstanceOf(ParseException.class)
77
+
.hasMessage("Empty expression");
78
+
}
79
+
80
+
@Test
81
+
void testDoubleStructureOperator() {
82
+
List<Token> tokens =
83
+
List.of(new Token(1, ".", STRUCTURE_SEPARATOR), new Token(2, ".", STRUCTURE_SEPARATOR));
84
+
assertThatThrownBy(new ShuntingYardConverter("..", tokens, configuration)::toAbstractSyntaxTree)
85
+
.isInstanceOf(ParseException.class)
86
+
.hasMessage("Missing operand for operator");
87
+
}
88
+
89
+
@Test
90
+
void testStructureFollowsPostfixOperator() {
91
+
List<Token> tokens =
92
+
List.of(new Token(1, ".", STRUCTURE_SEPARATOR), new Token(2, "!", POSTFIX_OPERATOR));
93
+
assertThatThrownBy(new ShuntingYardConverter("..", tokens, configuration)::toAbstractSyntaxTree)
94
+
.isInstanceOf(ParseException.class)
95
+
.hasMessage("Missing operand for operator");
96
+
}
97
+
98
+
@Test
99
+
void testStructureFollowsTwoPostfixOperators() {
100
+
List<Token> tokens =
101
+
List.of(
102
+
new Token(1, ".", STRUCTURE_SEPARATOR),
103
+
new Token(2, "!", POSTFIX_OPERATOR),
104
+
new Token(2, "!", POSTFIX_OPERATOR));
105
+
assertThatThrownBy(new ShuntingYardConverter("..", tokens, configuration)::toAbstractSyntaxTree)
106
+
.isInstanceOf(ParseException.class)
107
+
.hasMessage("Missing operand for operator");
108
+
}
109
+
110
+
@Test
111
+
void testFunctionNotEnoughParameters() {
112
+
Expression expression = new Expression("ROUND(2)");
113
+
114
+
assertThatThrownBy(expression::evaluate)
115
+
.isInstanceOf(ParseException.class)
116
+
.hasMessage("Not enough parameters for function");
117
+
}
118
+
119
+
@Test
120
+
void testFunctionNotEnoughParametersForVarArgs() {
121
+
Expression expression = new Expression("MIN()");
122
+
123
+
assertThatThrownBy(expression::evaluate)
124
+
.isInstanceOf(ParseException.class)
125
+
.hasMessage("Not enough parameters for function");
126
+
}
127
+
128
+
@Test
129
+
void testFunctionTooManyParameters() {
130
+
Expression expression = new Expression("ROUND(1,2,3)");
131
+
132
+
assertThatThrownBy(expression::evaluate)
133
+
.isInstanceOf(ParseException.class)
134
+
.hasMessage("Too many parameters for function");
135
+
}
136
+
}
+61
src/test/java/com/ezylang/evalex/parser/ShuntingYardStructureTest.java
+61
src/test/java/com/ezylang/evalex/parser/ShuntingYardStructureTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
19
+
20
+
import com.ezylang.evalex.Expression;
21
+
import org.junit.jupiter.api.Test;
22
+
23
+
class ShuntingYardStructureTest extends BaseParserTest {
24
+
25
+
@Test
26
+
void testSimpleStructure() throws ParseException {
27
+
assertASTTreeIsEqualTo(
28
+
"a.b",
29
+
"{\"type\":\"STRUCTURE_SEPARATOR\",\"value\":\".\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"a\"},{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"b\"}]}");
30
+
}
31
+
32
+
@Test
33
+
void testTripleStructure() throws ParseException {
34
+
assertASTTreeIsEqualTo(
35
+
"a.b.c",
36
+
"{\"type\":\"STRUCTURE_SEPARATOR\",\"value\":\".\",\"children\":[{\"type\":\"STRUCTURE_SEPARATOR\",\"value\":\".\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"a\"},{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"b\"}]},{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"c\"}]}");
37
+
}
38
+
39
+
@Test
40
+
void testArrayCombination() throws ParseException {
41
+
assertASTTreeIsEqualTo(
42
+
"order[4].position[2].amount",
43
+
"{\"type\":\"STRUCTURE_SEPARATOR\",\"value\":\".\",\"children\":[{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"STRUCTURE_SEPARATOR\",\"value\":\".\",\"children\":[{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"order\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"4\"}]},{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"position\"}]},{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"}]},{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"amount\"}]}");
44
+
}
45
+
46
+
@Test
47
+
void testExceptionStructureEnd() {
48
+
Expression expression = new Expression("a.");
49
+
assertThatThrownBy(expression::getAbstractSyntaxTree)
50
+
.isInstanceOf(ParseException.class)
51
+
.hasMessage("Missing second operand for operator");
52
+
}
53
+
54
+
@Test
55
+
void testExceptionStructureStart() {
56
+
Expression expression = new Expression(".a");
57
+
assertThatThrownBy(expression::getAbstractSyntaxTree)
58
+
.isInstanceOf(ParseException.class)
59
+
.hasMessage("Structure separator not allowed here");
60
+
}
61
+
}
+77
src/test/java/com/ezylang/evalex/parser/TokenTest.java
+77
src/test/java/com/ezylang/evalex/parser/TokenTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThat;
19
+
20
+
import com.ezylang.evalex.config.ExpressionConfiguration;
21
+
import com.ezylang.evalex.parser.Token.TokenType;
22
+
import org.junit.jupiter.api.Test;
23
+
24
+
class TokenTest {
25
+
26
+
final ExpressionConfiguration expressionConfiguration =
27
+
ExpressionConfiguration.defaultConfiguration();
28
+
29
+
@Test
30
+
void testTokenCreation() {
31
+
int counter = 0;
32
+
for (TokenType type : TokenType.values()) {
33
+
counter++;
34
+
String tokenString =
35
+
type == TokenType.NUMBER_LITERAL ? Integer.toString(counter) : "token" + counter;
36
+
Token token = new Token(counter, tokenString, type);
37
+
38
+
assertThat(token.getType()).isEqualTo(type);
39
+
assertThat(token.getStartPosition()).isEqualTo(counter);
40
+
assertThat(token.getValue()).isEqualTo(tokenString);
41
+
assertThat(token.getFunctionDefinition()).isNull();
42
+
assertThat(token.getOperatorDefinition()).isNull();
43
+
}
44
+
}
45
+
46
+
@Test
47
+
void testFunctionToken() {
48
+
Token token =
49
+
new Token(
50
+
3,
51
+
"MAX",
52
+
TokenType.FUNCTION,
53
+
expressionConfiguration.getFunctionDictionary().getFunction("MAX"));
54
+
55
+
assertThat(token.getStartPosition()).isEqualTo(3);
56
+
assertThat(token.getValue()).isEqualTo("MAX");
57
+
assertThat(token.getType()).isEqualTo(TokenType.FUNCTION);
58
+
assertThat(token.getFunctionDefinition()).isNotNull();
59
+
assertThat(token.getOperatorDefinition()).isNull();
60
+
}
61
+
62
+
@Test
63
+
void testOperatorToken() {
64
+
Token token =
65
+
new Token(
66
+
1,
67
+
"+",
68
+
TokenType.INFIX_OPERATOR,
69
+
expressionConfiguration.getOperatorDictionary().getInfixOperator("+"));
70
+
71
+
assertThat(token.getStartPosition()).isEqualTo(1);
72
+
assertThat(token.getValue()).isEqualTo("+");
73
+
assertThat(token.getType()).isEqualTo(TokenType.INFIX_OPERATOR);
74
+
assertThat(token.getFunctionDefinition()).isNull();
75
+
assertThat(token.getOperatorDefinition()).isNotNull();
76
+
}
77
+
}
+111
src/test/java/com/ezylang/evalex/parser/TokenizerArrayTest.java
+111
src/test/java/com/ezylang/evalex/parser/TokenizerArrayTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
19
+
20
+
import com.ezylang.evalex.config.ExpressionConfiguration;
21
+
import com.ezylang.evalex.parser.Token.TokenType;
22
+
import org.junit.jupiter.api.Test;
23
+
24
+
class TokenizerArrayTest extends BaseParserTest {
25
+
26
+
@Test
27
+
void testArraySimple() throws ParseException {
28
+
assertAllTokensParsedCorrectly(
29
+
"a[1]",
30
+
new Token(1, "a", TokenType.VARIABLE_OR_CONSTANT),
31
+
new Token(2, "[", TokenType.ARRAY_OPEN),
32
+
new Token(3, "1", TokenType.NUMBER_LITERAL),
33
+
new Token(4, "]", TokenType.ARRAY_CLOSE));
34
+
}
35
+
36
+
@Test
37
+
void testArrayNested() throws ParseException {
38
+
assertAllTokensParsedCorrectly(
39
+
"a[b[2] + c[3]]",
40
+
new Token(1, "a", TokenType.VARIABLE_OR_CONSTANT),
41
+
new Token(2, "[", TokenType.ARRAY_OPEN),
42
+
new Token(3, "b", TokenType.VARIABLE_OR_CONSTANT),
43
+
new Token(4, "[", TokenType.ARRAY_OPEN),
44
+
new Token(5, "2", TokenType.NUMBER_LITERAL),
45
+
new Token(6, "]", TokenType.ARRAY_CLOSE),
46
+
new Token(8, "+", TokenType.INFIX_OPERATOR),
47
+
new Token(10, "c", TokenType.VARIABLE_OR_CONSTANT),
48
+
new Token(11, "[", TokenType.ARRAY_OPEN),
49
+
new Token(12, "3", TokenType.NUMBER_LITERAL),
50
+
new Token(13, "]", TokenType.ARRAY_CLOSE),
51
+
new Token(14, "]", TokenType.ARRAY_CLOSE));
52
+
}
53
+
54
+
@Test
55
+
void testMissingClosingArray() {
56
+
assertThatThrownBy(() -> new Tokenizer("a[2+4", configuration).parse())
57
+
.isEqualTo(new ParseException(1, 5, "a[2+4", "Closing array not found"));
58
+
}
59
+
60
+
@Test
61
+
void testUnexpectedClosingArray() {
62
+
assertThatThrownBy(() -> new Tokenizer("a[2+4]]", configuration).parse())
63
+
.isEqualTo(new ParseException(7, 7, "]", "Unexpected closing array"));
64
+
}
65
+
66
+
@Test
67
+
void testOpenArrayNotAllowedBeginning() {
68
+
assertThatThrownBy(() -> new Tokenizer("[1]", configuration).parse())
69
+
.isEqualTo(new ParseException(1, 1, "[", "Array open not allowed here"));
70
+
}
71
+
72
+
@Test
73
+
void testOpenArrayNotAllowedAfterOperator() {
74
+
assertThatThrownBy(() -> new Tokenizer("1+[1]", configuration).parse())
75
+
.isEqualTo(new ParseException(3, 3, "[", "Array open not allowed here"));
76
+
}
77
+
78
+
@Test
79
+
void testOpenArrayNotAllowedAfterBrace() {
80
+
assertThatThrownBy(() -> new Tokenizer("([1]", configuration).parse())
81
+
.isEqualTo(new ParseException(2, 2, "[", "Array open not allowed here"));
82
+
}
83
+
84
+
@Test
85
+
void testCloseArrayNotAllowedBeginning() {
86
+
assertThatThrownBy(() -> new Tokenizer("]", configuration).parse())
87
+
.isEqualTo(new ParseException(1, 1, "]", "Array close not allowed here"));
88
+
}
89
+
90
+
@Test
91
+
void testCloseArrayNotAllowedAfterBrace() {
92
+
assertThatThrownBy(() -> new Tokenizer("(]", configuration).parse())
93
+
.isEqualTo(new ParseException(2, 2, "]", "Array close not allowed here"));
94
+
}
95
+
96
+
@Test
97
+
void testArraysNotAllowedOpen() {
98
+
ExpressionConfiguration config = ExpressionConfiguration.builder().arraysAllowed(false).build();
99
+
100
+
assertThatThrownBy(() -> new Tokenizer("a[0]", config).parse())
101
+
.isEqualTo(new ParseException(2, 2, "[", "Undefined operator '['"));
102
+
}
103
+
104
+
@Test
105
+
void testArraysNotAllowedClose() {
106
+
ExpressionConfiguration config = ExpressionConfiguration.builder().arraysAllowed(false).build();
107
+
108
+
assertThatThrownBy(() -> new Tokenizer("]", config).parse())
109
+
.isEqualTo(new ParseException(1, 1, "]", "Undefined operator ']'"));
110
+
}
111
+
}
+63
src/test/java/com/ezylang/evalex/parser/TokenizerBracesTest.java
+63
src/test/java/com/ezylang/evalex/parser/TokenizerBracesTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
19
+
20
+
import com.ezylang.evalex.parser.Token.TokenType;
21
+
import org.junit.jupiter.api.Test;
22
+
23
+
class TokenizerBracesTest extends BaseParserTest {
24
+
25
+
@Test
26
+
void testBracesSimple() throws ParseException {
27
+
assertAllTokensParsedCorrectly(
28
+
"(a + b)",
29
+
new Token(1, "(", TokenType.BRACE_OPEN),
30
+
new Token(2, "a", TokenType.VARIABLE_OR_CONSTANT),
31
+
new Token(4, "+", TokenType.INFIX_OPERATOR),
32
+
new Token(6, "b", TokenType.VARIABLE_OR_CONSTANT),
33
+
new Token(7, ")", TokenType.BRACE_CLOSE));
34
+
}
35
+
36
+
@Test
37
+
void testBracesNested() throws ParseException {
38
+
39
+
assertAllTokensParsedCorrectly(
40
+
"(a+(b+c))",
41
+
new Token(1, "(", TokenType.BRACE_OPEN),
42
+
new Token(2, "a", TokenType.VARIABLE_OR_CONSTANT),
43
+
new Token(3, "+", TokenType.INFIX_OPERATOR),
44
+
new Token(4, "(", TokenType.BRACE_OPEN),
45
+
new Token(5, "b", TokenType.VARIABLE_OR_CONSTANT),
46
+
new Token(6, "+", TokenType.INFIX_OPERATOR),
47
+
new Token(7, "c", TokenType.VARIABLE_OR_CONSTANT),
48
+
new Token(8, ")", TokenType.BRACE_CLOSE),
49
+
new Token(9, ")", TokenType.BRACE_CLOSE));
50
+
}
51
+
52
+
@Test
53
+
void testMissingClosingBrace() {
54
+
assertThatThrownBy(() -> new Tokenizer("(2+4", configuration).parse())
55
+
.isEqualTo(new ParseException(1, 4, "(2+4", "Closing brace not found"));
56
+
}
57
+
58
+
@Test
59
+
void testUnexpectedClosingBrace() {
60
+
assertThatThrownBy(() -> new Tokenizer("(2+4))", configuration).parse())
61
+
.isEqualTo(new ParseException(6, 6, ")", "Unexpected closing brace"));
62
+
}
63
+
}
+60
src/test/java/com/ezylang/evalex/parser/TokenizerExpressionTest.java
+60
src/test/java/com/ezylang/evalex/parser/TokenizerExpressionTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
19
+
20
+
import com.ezylang.evalex.parser.Token.TokenType;
21
+
import org.junit.jupiter.api.Test;
22
+
23
+
class TokenizerExpressionTest extends BaseParserTest {
24
+
25
+
@Test
26
+
void testSingleNumber() throws ParseException {
27
+
assertAllTokensParsedCorrectly("1", new Token(1, "1", TokenType.NUMBER_LITERAL));
28
+
}
29
+
30
+
@Test
31
+
void testSingleVariable() throws ParseException {
32
+
assertAllTokensParsedCorrectly("a", new Token(1, "a", TokenType.VARIABLE_OR_CONSTANT));
33
+
}
34
+
35
+
@Test
36
+
void testSimple() throws ParseException {
37
+
assertAllTokensParsedCorrectly(
38
+
"a+123",
39
+
new Token(1, "a", TokenType.VARIABLE_OR_CONSTANT),
40
+
new Token(2, "+", TokenType.INFIX_OPERATOR),
41
+
new Token(3, "123", TokenType.NUMBER_LITERAL));
42
+
}
43
+
44
+
@Test
45
+
void testTwo() throws ParseException {
46
+
assertAllTokensParsedCorrectly(
47
+
"a+123+c",
48
+
new Token(1, "a", TokenType.VARIABLE_OR_CONSTANT),
49
+
new Token(2, "+", TokenType.INFIX_OPERATOR),
50
+
new Token(3, "123", TokenType.NUMBER_LITERAL),
51
+
new Token(6, "+", TokenType.INFIX_OPERATOR),
52
+
new Token(7, "c", TokenType.VARIABLE_OR_CONSTANT));
53
+
}
54
+
55
+
@Test
56
+
void testUndefinedOperator() {
57
+
assertThatThrownBy(() -> new Tokenizer("a $ b", configuration).parse())
58
+
.isEqualTo(new ParseException(3, 3, "$", "Undefined operator '$'"));
59
+
}
60
+
}
+142
src/test/java/com/ezylang/evalex/parser/TokenizerFunctionsTest.java
+142
src/test/java/com/ezylang/evalex/parser/TokenizerFunctionsTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
19
+
20
+
import com.ezylang.evalex.config.TestConfigurationProvider.DummyFunction;
21
+
import com.ezylang.evalex.parser.Token.TokenType;
22
+
import org.junit.jupiter.api.Test;
23
+
24
+
class TokenizerFunctionsTest extends BaseParserTest {
25
+
26
+
@Test
27
+
void testSimple() throws ParseException {
28
+
configuration.getFunctionDictionary().addFunction("f", new DummyFunction());
29
+
assertAllTokensParsedCorrectly(
30
+
"f(x)",
31
+
new Token(1, "f", TokenType.FUNCTION),
32
+
new Token(2, "(", TokenType.BRACE_OPEN),
33
+
new Token(3, "x", TokenType.VARIABLE_OR_CONSTANT),
34
+
new Token(4, ")", TokenType.BRACE_CLOSE));
35
+
}
36
+
37
+
@Test
38
+
void testBlanks() throws ParseException {
39
+
configuration.getFunctionDictionary().addFunction("f", new DummyFunction());
40
+
assertAllTokensParsedCorrectly(
41
+
"f (x)",
42
+
new Token(1, "f", TokenType.FUNCTION),
43
+
new Token(3, "(", TokenType.BRACE_OPEN),
44
+
new Token(4, "x", TokenType.VARIABLE_OR_CONSTANT),
45
+
new Token(5, ")", TokenType.BRACE_CLOSE));
46
+
}
47
+
48
+
@Test
49
+
void testUnderscores() throws ParseException {
50
+
configuration.getFunctionDictionary().addFunction("_f_x_", new DummyFunction());
51
+
assertAllTokensParsedCorrectly(
52
+
"_f_x_(x)",
53
+
new Token(1, "_f_x_", TokenType.FUNCTION),
54
+
new Token(6, "(", TokenType.BRACE_OPEN),
55
+
new Token(7, "x", TokenType.VARIABLE_OR_CONSTANT),
56
+
new Token(8, ")", TokenType.BRACE_CLOSE));
57
+
}
58
+
59
+
@Test
60
+
void testWithNumbers() throws ParseException {
61
+
configuration.getFunctionDictionary().addFunction("f1x2", new DummyFunction());
62
+
assertAllTokensParsedCorrectly(
63
+
"f1x2(x)",
64
+
new Token(1, "f1x2", TokenType.FUNCTION),
65
+
new Token(5, "(", TokenType.BRACE_OPEN),
66
+
new Token(6, "x", TokenType.VARIABLE_OR_CONSTANT),
67
+
new Token(7, ")", TokenType.BRACE_CLOSE));
68
+
}
69
+
70
+
@Test
71
+
void testWithMoreParameters() throws ParseException {
72
+
assertAllTokensParsedCorrectly(
73
+
"SUM(a, 2, 3)",
74
+
new Token(1, "SUM", TokenType.FUNCTION),
75
+
new Token(4, "(", TokenType.BRACE_OPEN),
76
+
new Token(5, "a", TokenType.VARIABLE_OR_CONSTANT),
77
+
new Token(6, ",", TokenType.COMMA),
78
+
new Token(8, "2", TokenType.NUMBER_LITERAL),
79
+
new Token(9, ",", TokenType.COMMA),
80
+
new Token(11, "3", TokenType.NUMBER_LITERAL),
81
+
new Token(12, ")", TokenType.BRACE_CLOSE));
82
+
}
83
+
84
+
@Test
85
+
void testWithMixedParameters() throws ParseException {
86
+
assertAllTokensParsedCorrectly(
87
+
"TEST(a, \"hello\", 3)",
88
+
new Token(1, "TEST", TokenType.FUNCTION),
89
+
new Token(5, "(", TokenType.BRACE_OPEN),
90
+
new Token(6, "a", TokenType.VARIABLE_OR_CONSTANT),
91
+
new Token(7, ",", TokenType.COMMA),
92
+
new Token(9, "hello", TokenType.STRING_LITERAL),
93
+
new Token(16, ",", TokenType.COMMA),
94
+
new Token(18, "3", TokenType.NUMBER_LITERAL),
95
+
new Token(19, ")", TokenType.BRACE_CLOSE));
96
+
}
97
+
98
+
@Test
99
+
void testFunctionInParameter() throws ParseException {
100
+
assertAllTokensParsedCorrectly(
101
+
"TEST(a, FACT(x), 3)",
102
+
new Token(1, "TEST", TokenType.FUNCTION),
103
+
new Token(5, "(", TokenType.BRACE_OPEN),
104
+
new Token(6, "a", TokenType.VARIABLE_OR_CONSTANT),
105
+
new Token(7, ",", TokenType.COMMA),
106
+
new Token(9, "FACT", TokenType.FUNCTION),
107
+
new Token(13, "(", TokenType.BRACE_OPEN),
108
+
new Token(14, "x", TokenType.VARIABLE_OR_CONSTANT),
109
+
new Token(15, ")", TokenType.BRACE_CLOSE),
110
+
new Token(16, ",", TokenType.COMMA),
111
+
new Token(18, "3", TokenType.NUMBER_LITERAL),
112
+
new Token(19, ")", TokenType.BRACE_CLOSE));
113
+
}
114
+
115
+
@Test
116
+
void testFunctionInParameterInFunctionParameter() throws ParseException {
117
+
assertAllTokensParsedCorrectly(
118
+
"SUM(a,FACT(MIN(x,y)),3)",
119
+
new Token(1, "SUM", TokenType.FUNCTION),
120
+
new Token(4, "(", TokenType.BRACE_OPEN),
121
+
new Token(5, "a", TokenType.VARIABLE_OR_CONSTANT),
122
+
new Token(6, ",", TokenType.COMMA),
123
+
new Token(7, "FACT", TokenType.FUNCTION),
124
+
new Token(11, "(", TokenType.BRACE_OPEN),
125
+
new Token(12, "MIN", TokenType.FUNCTION),
126
+
new Token(15, "(", TokenType.BRACE_OPEN),
127
+
new Token(16, "x", TokenType.VARIABLE_OR_CONSTANT),
128
+
new Token(17, ",", TokenType.COMMA),
129
+
new Token(18, "y", TokenType.VARIABLE_OR_CONSTANT),
130
+
new Token(19, ")", TokenType.BRACE_CLOSE),
131
+
new Token(20, ")", TokenType.BRACE_CLOSE),
132
+
new Token(21, ",", TokenType.COMMA),
133
+
new Token(22, "3", TokenType.NUMBER_LITERAL),
134
+
new Token(23, ")", TokenType.BRACE_CLOSE));
135
+
}
136
+
137
+
@Test
138
+
void testUndefinedFunction() {
139
+
assertThatThrownBy(() -> new Tokenizer("a(b)", configuration).parse())
140
+
.isEqualTo(new ParseException(1, 2, "a", "Undefined function 'a'"));
141
+
}
142
+
}
+62
src/test/java/com/ezylang/evalex/parser/TokenizerImplicitMultiplicationTest.java
+62
src/test/java/com/ezylang/evalex/parser/TokenizerImplicitMultiplicationTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
19
+
20
+
import com.ezylang.evalex.config.ExpressionConfiguration;
21
+
import com.ezylang.evalex.parser.Token.TokenType;
22
+
import org.junit.jupiter.api.Test;
23
+
24
+
class TokenizerImplicitMultiplicationTest extends BaseParserTest {
25
+
26
+
@Test
27
+
void testImplicitBraces() throws ParseException {
28
+
assertAllTokensParsedCorrectly(
29
+
"(a+b)(a-b)",
30
+
new Token(1, "(", TokenType.BRACE_OPEN),
31
+
new Token(2, "a", TokenType.VARIABLE_OR_CONSTANT),
32
+
new Token(3, "+", TokenType.INFIX_OPERATOR),
33
+
new Token(4, "b", TokenType.VARIABLE_OR_CONSTANT),
34
+
new Token(5, ")", TokenType.BRACE_CLOSE),
35
+
new Token(6, "*", TokenType.INFIX_OPERATOR),
36
+
new Token(6, "(", TokenType.BRACE_OPEN),
37
+
new Token(7, "a", TokenType.VARIABLE_OR_CONSTANT),
38
+
new Token(8, "-", TokenType.INFIX_OPERATOR),
39
+
new Token(9, "b", TokenType.VARIABLE_OR_CONSTANT),
40
+
new Token(10, ")", TokenType.BRACE_CLOSE));
41
+
}
42
+
43
+
@Test
44
+
void testImplicitNumber() throws ParseException {
45
+
assertAllTokensParsedCorrectly(
46
+
"2(x)",
47
+
new Token(1, "2", TokenType.NUMBER_LITERAL),
48
+
new Token(2, "*", TokenType.INFIX_OPERATOR),
49
+
new Token(2, "(", TokenType.BRACE_OPEN),
50
+
new Token(3, "x", TokenType.VARIABLE_OR_CONSTANT),
51
+
new Token(4, ")", TokenType.BRACE_CLOSE));
52
+
}
53
+
54
+
@Test
55
+
void testImplicitMultiplicationNotAllowed() {
56
+
ExpressionConfiguration config =
57
+
ExpressionConfiguration.builder().implicitMultiplicationAllowed(false).build();
58
+
59
+
assertThatThrownBy(() -> new Tokenizer("2(x+y)", config).parse())
60
+
.isEqualTo(new ParseException(2, 2, "(", "Missing operator"));
61
+
}
62
+
}
+95
src/test/java/com/ezylang/evalex/parser/TokenizerLiteralOperatorsTest.java
+95
src/test/java/com/ezylang/evalex/parser/TokenizerLiteralOperatorsTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import static com.ezylang.evalex.operators.OperatorIfc.OPERATOR_PRECEDENCE_AND;
19
+
import static com.ezylang.evalex.operators.OperatorIfc.OPERATOR_PRECEDENCE_OR;
20
+
21
+
import com.ezylang.evalex.Expression;
22
+
import com.ezylang.evalex.data.EvaluationValue;
23
+
import com.ezylang.evalex.operators.AbstractOperator;
24
+
import com.ezylang.evalex.operators.InfixOperator;
25
+
import com.ezylang.evalex.operators.PostfixOperator;
26
+
import com.ezylang.evalex.operators.PrefixOperator;
27
+
import com.ezylang.evalex.parser.Token.TokenType;
28
+
import java.util.Map;
29
+
import org.junit.jupiter.api.BeforeEach;
30
+
import org.junit.jupiter.api.Test;
31
+
32
+
class TokenizerLiteralOperatorsTest extends BaseParserTest {
33
+
34
+
@BeforeEach
35
+
public void setup() {
36
+
configuration =
37
+
configuration.withAdditionalOperators(
38
+
Map.entry("AND", new AndOperator()),
39
+
Map.entry("OR", new OrOperator()),
40
+
Map.entry("NOT", new NotOperator()),
41
+
Map.entry("DENIED", new DeniedOperator()));
42
+
}
43
+
44
+
@Test
45
+
void testAndOrNot() throws ParseException {
46
+
assertAllTokensParsedCorrectly(
47
+
"NOT a AND b DENIED OR NOT(c)",
48
+
new Token(1, "NOT", TokenType.PREFIX_OPERATOR),
49
+
new Token(5, "a", TokenType.VARIABLE_OR_CONSTANT),
50
+
new Token(7, "AND", TokenType.INFIX_OPERATOR),
51
+
new Token(11, "b", TokenType.VARIABLE_OR_CONSTANT),
52
+
new Token(13, "DENIED", TokenType.POSTFIX_OPERATOR),
53
+
new Token(20, "OR", TokenType.INFIX_OPERATOR),
54
+
new Token(23, "NOT", TokenType.PREFIX_OPERATOR),
55
+
new Token(26, "(", TokenType.BRACE_OPEN),
56
+
new Token(27, "c", TokenType.VARIABLE_OR_CONSTANT),
57
+
new Token(28, ")", TokenType.BRACE_CLOSE));
58
+
}
59
+
60
+
@InfixOperator(precedence = OPERATOR_PRECEDENCE_AND)
61
+
static class AndOperator extends AbstractOperator {
62
+
@Override
63
+
public EvaluationValue evaluate(
64
+
Expression expression, Token operatorToken, EvaluationValue... operands) {
65
+
return new EvaluationValue(operands[0].getBooleanValue() && operands[1].getBooleanValue());
66
+
}
67
+
}
68
+
69
+
@InfixOperator(precedence = OPERATOR_PRECEDENCE_OR)
70
+
static class OrOperator extends AbstractOperator {
71
+
@Override
72
+
public EvaluationValue evaluate(
73
+
Expression expression, Token operatorToken, EvaluationValue... operands) {
74
+
return new EvaluationValue(operands[0].getBooleanValue() || operands[1].getBooleanValue());
75
+
}
76
+
}
77
+
78
+
@PrefixOperator(leftAssociative = false)
79
+
static class NotOperator extends AbstractOperator {
80
+
@Override
81
+
public EvaluationValue evaluate(
82
+
Expression expression, Token operatorToken, EvaluationValue... operands) {
83
+
return new EvaluationValue(!operands[0].getBooleanValue());
84
+
}
85
+
}
86
+
87
+
@PostfixOperator()
88
+
static class DeniedOperator extends AbstractOperator {
89
+
@Override
90
+
public EvaluationValue evaluate(
91
+
Expression expression, Token operatorToken, EvaluationValue... operands) {
92
+
return new EvaluationValue(!operands[0].getBooleanValue());
93
+
}
94
+
}
95
+
}
+73
src/test/java/com/ezylang/evalex/parser/TokenizerNumberLiteralTest.java
+73
src/test/java/com/ezylang/evalex/parser/TokenizerNumberLiteralTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import com.ezylang.evalex.parser.Token.TokenType;
19
+
import org.junit.jupiter.api.Test;
20
+
21
+
class TokenizerNumberLiteralTest extends BaseParserTest {
22
+
23
+
@Test
24
+
void testSingleDigit() throws ParseException {
25
+
assertAllTokensParsedCorrectly("7", new Token(1, "7", TokenType.NUMBER_LITERAL));
26
+
}
27
+
28
+
@Test
29
+
void testMultipleDigit() throws ParseException {
30
+
assertAllTokensParsedCorrectly("888", new Token(1, "888", TokenType.NUMBER_LITERAL));
31
+
}
32
+
33
+
@Test
34
+
void testBlanks() throws ParseException {
35
+
assertAllTokensParsedCorrectly("\t 123 \r\n", new Token(3, "123", TokenType.NUMBER_LITERAL));
36
+
}
37
+
38
+
@Test
39
+
void testDecimal() throws ParseException {
40
+
assertAllTokensParsedCorrectly("123.834", new Token(1, "123.834", TokenType.NUMBER_LITERAL));
41
+
}
42
+
43
+
@Test
44
+
void testDecimalStart() throws ParseException {
45
+
assertAllTokensParsedCorrectly(".9", new Token(1, ".9", TokenType.NUMBER_LITERAL));
46
+
}
47
+
48
+
@Test
49
+
void testDecimalEnd() throws ParseException {
50
+
assertAllTokensParsedCorrectly("123.", new Token(1, "123.", TokenType.NUMBER_LITERAL));
51
+
}
52
+
53
+
@Test
54
+
void testHexNumberSimple() throws ParseException {
55
+
assertAllTokensParsedCorrectly("0x0", new Token(1, "0x0", TokenType.NUMBER_LITERAL));
56
+
}
57
+
58
+
@Test
59
+
void testHexNumberLong() throws ParseException {
60
+
assertAllTokensParsedCorrectly(
61
+
"0x3ABCDEF0", new Token(1, "0x3ABCDEF0", TokenType.NUMBER_LITERAL));
62
+
assertAllTokensParsedCorrectly(
63
+
" \t0x3abcdefAbcdef09873EE ",
64
+
new Token(3, "0x3abcdefAbcdef09873EE", TokenType.NUMBER_LITERAL));
65
+
}
66
+
67
+
@Test
68
+
void testHexNumberBlank() throws ParseException {
69
+
assertAllTokensParsedCorrectly(
70
+
" \t0x3abcdefAbcdef09873EE ",
71
+
new Token(3, "0x3abcdefAbcdef09873EE", TokenType.NUMBER_LITERAL));
72
+
}
73
+
}
+98
src/test/java/com/ezylang/evalex/parser/TokenizerOperatorSeparationTest.java
+98
src/test/java/com/ezylang/evalex/parser/TokenizerOperatorSeparationTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import com.ezylang.evalex.parser.Token.TokenType;
19
+
import org.junit.jupiter.api.Test;
20
+
21
+
class TokenizerOperatorSeparationTest extends BaseParserTest {
22
+
23
+
@Test
24
+
void testInfixPrefix() throws ParseException {
25
+
assertAllTokensParsedCorrectly(
26
+
"2+-3",
27
+
new Token(1, "2", TokenType.NUMBER_LITERAL),
28
+
new Token(2, "+", TokenType.INFIX_OPERATOR),
29
+
new Token(3, "-", TokenType.PREFIX_OPERATOR),
30
+
new Token(4, "3", TokenType.NUMBER_LITERAL));
31
+
}
32
+
33
+
@Test
34
+
void testPostfixInfix() throws ParseException {
35
+
assertAllTokensParsedCorrectly(
36
+
"a++-3",
37
+
new Token(1, "a", TokenType.VARIABLE_OR_CONSTANT),
38
+
new Token(2, "++", TokenType.POSTFIX_OPERATOR),
39
+
new Token(4, "-", TokenType.INFIX_OPERATOR),
40
+
new Token(5, "3", TokenType.NUMBER_LITERAL));
41
+
}
42
+
43
+
@Test
44
+
void testPostfixInfixPrefix() throws ParseException {
45
+
assertAllTokensParsedCorrectly(
46
+
"a++--3",
47
+
new Token(1, "a", TokenType.VARIABLE_OR_CONSTANT),
48
+
new Token(2, "++", TokenType.POSTFIX_OPERATOR),
49
+
new Token(4, "-", TokenType.INFIX_OPERATOR),
50
+
new Token(5, "-", TokenType.PREFIX_OPERATOR),
51
+
new Token(6, "3", TokenType.NUMBER_LITERAL));
52
+
}
53
+
54
+
@Test
55
+
void testPrefixPrefix() throws ParseException {
56
+
assertAllTokensParsedCorrectly(
57
+
"!-2",
58
+
new Token(1, "!", TokenType.PREFIX_OPERATOR),
59
+
new Token(2, "-", TokenType.PREFIX_OPERATOR),
60
+
new Token(3, "2", TokenType.NUMBER_LITERAL));
61
+
}
62
+
63
+
@Test
64
+
void testEqualsEqualsNumbers() throws ParseException {
65
+
assertAllTokensParsedCorrectly(
66
+
"3==3",
67
+
new Token(1, "3", TokenType.NUMBER_LITERAL),
68
+
new Token(2, "==", TokenType.INFIX_OPERATOR),
69
+
new Token(4, "3", TokenType.NUMBER_LITERAL));
70
+
}
71
+
72
+
@Test
73
+
void testEqualsEqualsVariables() throws ParseException {
74
+
assertAllTokensParsedCorrectly(
75
+
"a==b",
76
+
new Token(1, "a", TokenType.VARIABLE_OR_CONSTANT),
77
+
new Token(2, "==", TokenType.INFIX_OPERATOR),
78
+
new Token(4, "b", TokenType.VARIABLE_OR_CONSTANT));
79
+
}
80
+
81
+
@Test
82
+
void testEqualsNumbers() throws ParseException {
83
+
assertAllTokensParsedCorrectly(
84
+
"3=3",
85
+
new Token(1, "3", TokenType.NUMBER_LITERAL),
86
+
new Token(2, "=", TokenType.INFIX_OPERATOR),
87
+
new Token(3, "3", TokenType.NUMBER_LITERAL));
88
+
}
89
+
90
+
@Test
91
+
void testEqualsVariables() throws ParseException {
92
+
assertAllTokensParsedCorrectly(
93
+
"a=b",
94
+
new Token(1, "a", TokenType.VARIABLE_OR_CONSTANT),
95
+
new Token(2, "=", TokenType.INFIX_OPERATOR),
96
+
new Token(3, "b", TokenType.VARIABLE_OR_CONSTANT));
97
+
}
98
+
}
+120
src/test/java/com/ezylang/evalex/parser/TokenizerPrefixPostfixTest.java
+120
src/test/java/com/ezylang/evalex/parser/TokenizerPrefixPostfixTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
19
+
20
+
import com.ezylang.evalex.parser.Token.TokenType;
21
+
import org.junit.jupiter.api.Test;
22
+
23
+
class TokenizerPrefixPostfixTest extends BaseParserTest {
24
+
25
+
@Test
26
+
void testPrefixSingle() throws ParseException {
27
+
assertAllTokensParsedCorrectly(
28
+
"++a",
29
+
new Token(1, "++", TokenType.PREFIX_OPERATOR),
30
+
new Token(3, "a", TokenType.VARIABLE_OR_CONSTANT));
31
+
}
32
+
33
+
@Test
34
+
void testPostfixSingle() throws ParseException {
35
+
assertAllTokensParsedCorrectly(
36
+
"a++",
37
+
new Token(1, "a", TokenType.VARIABLE_OR_CONSTANT),
38
+
new Token(2, "++", TokenType.POSTFIX_OPERATOR));
39
+
}
40
+
41
+
@Test
42
+
void testPostfixAsPrefixThrowsException() {
43
+
assertThatThrownBy(new Tokenizer("?a", configuration)::parse)
44
+
.isInstanceOf(ParseException.class)
45
+
.hasMessage("Undefined operator '?'");
46
+
}
47
+
48
+
@Test
49
+
void testPrefixAndPostfix() throws ParseException {
50
+
// note: if this is supported, depends on the operator and what type it expects as operand
51
+
assertAllTokensParsedCorrectly(
52
+
"++a++",
53
+
new Token(1, "++", TokenType.PREFIX_OPERATOR),
54
+
new Token(3, "a", TokenType.VARIABLE_OR_CONSTANT),
55
+
new Token(4, "++", TokenType.POSTFIX_OPERATOR));
56
+
}
57
+
58
+
@Test
59
+
void testPrefixWithInfixAndPostfix() throws ParseException {
60
+
assertAllTokensParsedCorrectly(
61
+
"++a+a++",
62
+
new Token(1, "++", TokenType.PREFIX_OPERATOR),
63
+
new Token(3, "a", TokenType.VARIABLE_OR_CONSTANT),
64
+
new Token(4, "+", TokenType.INFIX_OPERATOR),
65
+
new Token(5, "a", TokenType.VARIABLE_OR_CONSTANT),
66
+
new Token(6, "++", TokenType.POSTFIX_OPERATOR));
67
+
}
68
+
69
+
@Test
70
+
void testPrefixWithBraces() throws ParseException {
71
+
assertAllTokensParsedCorrectly(
72
+
"(++a)+(a++)",
73
+
new Token(1, "(", TokenType.BRACE_OPEN),
74
+
new Token(2, "++", TokenType.PREFIX_OPERATOR),
75
+
new Token(4, "a", TokenType.VARIABLE_OR_CONSTANT),
76
+
new Token(5, ")", TokenType.BRACE_CLOSE),
77
+
new Token(6, "+", TokenType.INFIX_OPERATOR),
78
+
new Token(7, "(", TokenType.BRACE_OPEN),
79
+
new Token(8, "a", TokenType.VARIABLE_OR_CONSTANT),
80
+
new Token(9, "++", TokenType.POSTFIX_OPERATOR),
81
+
new Token(11, ")", TokenType.BRACE_CLOSE));
82
+
}
83
+
84
+
@Test
85
+
void testPrefixWithFunction() throws ParseException {
86
+
assertAllTokensParsedCorrectly(
87
+
"++MAX(++a,a++)++",
88
+
new Token(1, "++", TokenType.PREFIX_OPERATOR),
89
+
new Token(3, "MAX", TokenType.FUNCTION),
90
+
new Token(6, "(", TokenType.BRACE_OPEN),
91
+
new Token(7, "++", TokenType.PREFIX_OPERATOR),
92
+
new Token(9, "a", TokenType.VARIABLE_OR_CONSTANT),
93
+
new Token(10, ",", TokenType.COMMA),
94
+
new Token(11, "a", TokenType.VARIABLE_OR_CONSTANT),
95
+
new Token(12, "++", TokenType.POSTFIX_OPERATOR),
96
+
new Token(14, ")", TokenType.BRACE_CLOSE),
97
+
new Token(15, "++", TokenType.POSTFIX_OPERATOR));
98
+
}
99
+
100
+
@Test
101
+
void testPrefixWithStringLiteral() throws ParseException {
102
+
assertAllTokensParsedCorrectly(
103
+
"++\"hello\"++",
104
+
new Token(1, "++", TokenType.PREFIX_OPERATOR),
105
+
new Token(3, "hello", TokenType.STRING_LITERAL),
106
+
new Token(10, "++", TokenType.POSTFIX_OPERATOR));
107
+
}
108
+
109
+
@Test
110
+
void testPrefixWithUnaryAndBinary() throws ParseException {
111
+
assertAllTokensParsedCorrectly(
112
+
"-a - -b++",
113
+
new Token(1, "-", TokenType.PREFIX_OPERATOR),
114
+
new Token(2, "a", TokenType.VARIABLE_OR_CONSTANT),
115
+
new Token(4, "-", TokenType.INFIX_OPERATOR),
116
+
new Token(6, "-", TokenType.PREFIX_OPERATOR),
117
+
new Token(7, "b", TokenType.VARIABLE_OR_CONSTANT),
118
+
new Token(8, "++", TokenType.POSTFIX_OPERATOR));
119
+
}
120
+
}
+67
src/test/java/com/ezylang/evalex/parser/TokenizerStringLiteralTest.java
+67
src/test/java/com/ezylang/evalex/parser/TokenizerStringLiteralTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
19
+
20
+
import com.ezylang.evalex.parser.Token.TokenType;
21
+
import org.junit.jupiter.api.Test;
22
+
23
+
class TokenizerStringLiteralTest extends BaseParserTest {
24
+
25
+
@Test
26
+
void testSimpleQuote() throws ParseException {
27
+
assertAllTokensParsedCorrectly(
28
+
"\"Hello, World\"", new Token(1, "Hello, World", TokenType.STRING_LITERAL));
29
+
}
30
+
31
+
@Test
32
+
void testSimpleQuoteLeadingBlanks() throws ParseException {
33
+
assertAllTokensParsedCorrectly(
34
+
" \t\n \"Hello, World\"", new Token(6, "Hello, World", TokenType.STRING_LITERAL));
35
+
}
36
+
37
+
@Test
38
+
void testSimpleQuoteTrailingBlanks() throws ParseException {
39
+
assertAllTokensParsedCorrectly(
40
+
"\"Hello, World\" \t\n ", new Token(1, "Hello, World", TokenType.STRING_LITERAL));
41
+
}
42
+
43
+
@Test
44
+
void testSimpleQuoteOperation() throws ParseException {
45
+
assertAllTokensParsedCorrectly(
46
+
"\"Hello\" + \" \" + \"World\"",
47
+
new Token(1, "Hello", TokenType.STRING_LITERAL),
48
+
new Token(9, "+", TokenType.INFIX_OPERATOR),
49
+
new Token(11, " ", TokenType.STRING_LITERAL),
50
+
new Token(15, "+", TokenType.INFIX_OPERATOR),
51
+
new Token(17, "World", TokenType.STRING_LITERAL));
52
+
}
53
+
54
+
@Test
55
+
void testErrorUnmatchedQuoteStart() {
56
+
assertThatThrownBy(() -> new Tokenizer("\"hello", configuration).parse())
57
+
.isInstanceOf(ParseException.class)
58
+
.hasMessage("Closing quote not found");
59
+
}
60
+
61
+
@Test
62
+
void testErrorUnmatchedQuoteOffset() {
63
+
assertThatThrownBy(() -> new Tokenizer("test \"hello", configuration).parse())
64
+
.isInstanceOf(ParseException.class)
65
+
.hasMessage("Closing quote not found");
66
+
}
67
+
}
+55
src/test/java/com/ezylang/evalex/parser/TokenizerStructureTest.java
+55
src/test/java/com/ezylang/evalex/parser/TokenizerStructureTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
19
+
20
+
import com.ezylang.evalex.config.ExpressionConfiguration;
21
+
import com.ezylang.evalex.parser.Token.TokenType;
22
+
import org.junit.jupiter.api.Test;
23
+
24
+
class TokenizerStructureTest extends BaseParserTest {
25
+
26
+
@Test
27
+
void testStructureSimple() throws ParseException {
28
+
assertAllTokensParsedCorrectly(
29
+
"a.b",
30
+
new Token(1, "a", TokenType.VARIABLE_OR_CONSTANT),
31
+
new Token(2, ".", TokenType.STRUCTURE_SEPARATOR),
32
+
new Token(3, "b", TokenType.VARIABLE_OR_CONSTANT));
33
+
}
34
+
35
+
@Test
36
+
void testStructureSeparatorNotAllowedBegin() {
37
+
assertThatThrownBy(() -> new Tokenizer(".", configuration).parse())
38
+
.isEqualTo(new ParseException(1, 1, ".", "Structure separator not allowed here"));
39
+
}
40
+
41
+
@Test
42
+
void testStructureSeparatorNotAllowedOperator() {
43
+
assertThatThrownBy(() -> new Tokenizer("-.", configuration).parse())
44
+
.isEqualTo(new ParseException(2, 2, ".", "Structure separator not allowed here"));
45
+
}
46
+
47
+
@Test
48
+
void testStructureNotAllowed() {
49
+
ExpressionConfiguration config =
50
+
ExpressionConfiguration.builder().structuresAllowed(false).build();
51
+
52
+
assertThatThrownBy(() -> new Tokenizer("a.b", config).parse())
53
+
.isEqualTo(new ParseException(2, 2, ".", "Undefined operator '.'"));
54
+
}
55
+
}
+59
src/test/java/com/ezylang/evalex/parser/TokenizerVariableNameTest.java
+59
src/test/java/com/ezylang/evalex/parser/TokenizerVariableNameTest.java
···
1
+
/*
2
+
Copyright 2012-2022 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import com.ezylang.evalex.parser.Token.TokenType;
19
+
import org.junit.jupiter.api.Test;
20
+
21
+
class TokenizerVariableNameTest extends BaseParserTest {
22
+
23
+
@Test
24
+
void testSimple() throws ParseException {
25
+
assertAllTokensParsedCorrectly("a", new Token(1, "a", TokenType.VARIABLE_OR_CONSTANT));
26
+
}
27
+
28
+
@Test
29
+
void testEndWithNumber() throws ParseException {
30
+
assertAllTokensParsedCorrectly("var1", new Token(1, "var1", TokenType.VARIABLE_OR_CONSTANT));
31
+
}
32
+
33
+
@Test
34
+
void testContainsNumber() throws ParseException {
35
+
assertAllTokensParsedCorrectly(
36
+
"var2test", new Token(1, "var2test", TokenType.VARIABLE_OR_CONSTANT));
37
+
}
38
+
39
+
@Test
40
+
void testUnderscore() throws ParseException {
41
+
assertAllTokensParsedCorrectly(
42
+
"_var_2_", new Token(1, "_var_2_", TokenType.VARIABLE_OR_CONSTANT));
43
+
}
44
+
45
+
@Test
46
+
void testUmlaut() throws ParseException {
47
+
assertAllTokensParsedCorrectly("Grün", new Token(1, "Grün", TokenType.VARIABLE_OR_CONSTANT));
48
+
assertAllTokensParsedCorrectly(
49
+
"olá_enchanté_γεια_σας",
50
+
new Token(1, "olá_enchanté_γεια_σας", TokenType.VARIABLE_OR_CONSTANT));
51
+
}
52
+
53
+
@Test
54
+
void testSpecialAlphabetical() throws ParseException {
55
+
assertAllTokensParsedCorrectly(
56
+
"olá_enchanté_γεια_σας",
57
+
new Token(1, "olá_enchanté_γεια_σας", TokenType.VARIABLE_OR_CONSTANT));
58
+
}
59
+
}
-150
src/test/java/com/udojava/evalex/TestBooleans.java
-150
src/test/java/com/udojava/evalex/TestBooleans.java
···
1
-
package com.udojava.evalex;
2
-
3
-
import static org.junit.Assert.assertEquals;
4
-
import static org.junit.Assert.assertFalse;
5
-
import static org.junit.Assert.assertTrue;
6
-
7
-
import java.util.Iterator;
8
-
import org.junit.Test;
9
-
10
-
11
-
public class TestBooleans {
12
-
13
-
private void assertToken(String surface, Expression.TokenType type, Expression.Token actual) {
14
-
assertEquals(surface, actual.surface);
15
-
assertEquals(type, actual.type);
16
-
}
17
-
18
-
@Test
19
-
public void testIsBoolean() {
20
-
Expression e = new Expression("1==1");
21
-
assertTrue(e.isBoolean());
22
-
23
-
e = new Expression("a==b").with("a", "1").and("b", "2");
24
-
assertTrue(e.isBoolean());
25
-
26
-
e = new Expression("(1==1)||(c==a+b)");
27
-
assertTrue(e.isBoolean());
28
-
29
-
e = new Expression("(z+z==x-y)||(c==a+b)");
30
-
assertTrue(e.isBoolean());
31
-
32
-
e = new Expression("NOT(a+b)");
33
-
assertTrue(e.isBoolean());
34
-
35
-
e = new Expression("a+b");
36
-
assertFalse(e.isBoolean());
37
-
38
-
e = new Expression("(a==b)+(b==c)");
39
-
assertFalse(e.isBoolean());
40
-
41
-
e = new Expression("SQRT(2)");
42
-
assertFalse(e.isBoolean());
43
-
44
-
e = new Expression("SQRT(2) == SQRT(b)");
45
-
assertTrue(e.isBoolean());
46
-
47
-
e = new Expression("IF(a==b,x+y,x-y)");
48
-
assertFalse(e.isBoolean());
49
-
50
-
e = new Expression("IF(a==b,x==y,a==b)");
51
-
assertTrue(e.isBoolean());
52
-
}
53
-
54
-
@Test
55
-
public void testAndTokenizer() {
56
-
Expression e = new Expression("1&&0");
57
-
Iterator<Expression.Token> i = e.getExpressionTokenizer();
58
-
59
-
assertToken("1", Expression.TokenType.LITERAL, i.next());
60
-
assertToken("&&", Expression.TokenType.OPERATOR, i.next());
61
-
assertToken("0", Expression.TokenType.LITERAL, i.next());
62
-
}
63
-
64
-
@Test
65
-
public void testAndRPN() {
66
-
assertEquals("1 0 &&", new Expression("1&&0").toRPN());
67
-
}
68
-
69
-
@Test
70
-
public void testAndEval() {
71
-
assertEquals("0", new Expression("1&&0").eval().toString());
72
-
assertEquals("1", new Expression("1&&1").eval().toString());
73
-
assertEquals("0", new Expression("0&&0").eval().toString());
74
-
assertEquals("0", new Expression("0&&1").eval().toString());
75
-
}
76
-
77
-
@Test
78
-
public void testOrEval() {
79
-
assertEquals("1", new Expression("1||0").eval().toString());
80
-
assertEquals("1", new Expression("1||1").eval().toString());
81
-
assertEquals("0", new Expression("0||0").eval().toString());
82
-
assertEquals("1", new Expression("0||1").eval().toString());
83
-
}
84
-
85
-
@Test
86
-
public void testCompare() {
87
-
assertEquals("1", new Expression("2>1").eval().toString());
88
-
assertEquals("0", new Expression("2<1").eval().toString());
89
-
assertEquals("0", new Expression("1>2").eval().toString());
90
-
assertEquals("1", new Expression("1<2").eval().toString());
91
-
assertEquals("0", new Expression("1=2").eval().toString());
92
-
assertEquals("1", new Expression("1=1").eval().toString());
93
-
assertEquals("1", new Expression("1>=1").eval().toString());
94
-
assertEquals("1", new Expression("1.1>=1").eval().toString());
95
-
assertEquals("0", new Expression("1>=2").eval().toString());
96
-
assertEquals("1", new Expression("1<=1").eval().toString());
97
-
assertEquals("0", new Expression("1.1<=1").eval().toString());
98
-
assertEquals("1", new Expression("1<=2").eval().toString());
99
-
assertEquals("0", new Expression("1=2").eval().toString());
100
-
assertEquals("1", new Expression("1=1").eval().toString());
101
-
assertEquals("1", new Expression("1!=2").eval().toString());
102
-
assertEquals("0", new Expression("1!=1").eval().toString());
103
-
}
104
-
105
-
@Test
106
-
public void testCompareCombined() {
107
-
assertEquals("1", new Expression("(2>1)||(1=0)").eval().toString());
108
-
assertEquals("0", new Expression("(2>3)||(1=0)").eval().toString());
109
-
assertEquals("1", new Expression("(2>3)||(1=0)||(1&&1)").eval().toString());
110
-
}
111
-
112
-
@Test
113
-
public void testMixed() {
114
-
assertEquals("0", new Expression("1.5 * 7 = 3").eval().toString());
115
-
assertEquals("1", new Expression("1.5 * 7 = 10.5").eval().toString());
116
-
}
117
-
118
-
@Test
119
-
public void testNot() {
120
-
assertEquals("0", new Expression("not(1)").eval().toString());
121
-
assertEquals("1", new Expression("not(0)").eval().toString());
122
-
assertEquals("1", new Expression("not(1.5 * 7 = 3)").eval().toString());
123
-
assertEquals("0", new Expression("not(1.5 * 7 = 10.5)").eval().toString());
124
-
}
125
-
126
-
@Test
127
-
public void testConstants() {
128
-
assertEquals("1", new Expression("TRUE!=FALSE").eval().toString());
129
-
assertEquals("0", new Expression("TRUE==2").eval().toString());
130
-
assertEquals("1", new Expression("NOT(TRUE)==FALSE").eval().toString());
131
-
assertEquals("1", new Expression("NOT(FALSE)==TRUE").eval().toString());
132
-
assertEquals("0", new Expression("TRUE && FALSE").eval().toString());
133
-
assertEquals("1", new Expression("TRUE || FALSE").eval().toString());
134
-
}
135
-
136
-
@Test
137
-
public void testIf() {
138
-
assertEquals("5", new Expression("if(TRUE, 5, 3)").eval().toString());
139
-
assertEquals("3", new Expression("IF(FALSE, 5, 3)").eval().toString());
140
-
assertEquals("5.35", new Expression("If(2, 5.35, 3)").eval().toString());
141
-
}
142
-
143
-
@Test
144
-
public void testDecimals() {
145
-
assertEquals("0", new Expression("if(0.0, 1, 0)").eval().toPlainString());
146
-
assertEquals("0", new Expression("0.0 || 0.0").eval().toPlainString());
147
-
assertEquals("1", new Expression("not(0.0)").eval().toPlainString());
148
-
assertEquals("0", new Expression("0.0 && 0.0").eval().toPlainString());
149
-
}
150
-
}
-52
src/test/java/com/udojava/evalex/TestCaseInsensitive.java
-52
src/test/java/com/udojava/evalex/TestCaseInsensitive.java
···
1
-
package com.udojava.evalex;
2
-
3
-
import java.math.BigDecimal;
4
-
import java.util.List;
5
-
import org.junit.Assert;
6
-
import org.junit.Test;
7
-
8
-
/**
9
-
* Created by showdown on 2/28/2016 at 3:29 PM. Project EvalEx
10
-
*/
11
-
public class TestCaseInsensitive {
12
-
13
-
@Test
14
-
public void testVariableIsCaseInsensitive() {
15
-
16
-
Expression expression = new Expression("a");
17
-
expression.setVariable("A", new BigDecimal(20));
18
-
Assert.assertEquals(20, expression.eval().intValue());
19
-
20
-
expression = new Expression("a + B");
21
-
expression.setVariable("A", new BigDecimal(10));
22
-
expression.setVariable("b", new BigDecimal(10));
23
-
Assert.assertEquals(20, expression.eval().intValue());
24
-
25
-
expression = new Expression("a+B");
26
-
expression.setVariable("A", "c+d");
27
-
expression.setVariable("b", new BigDecimal(10));
28
-
expression.setVariable("C", new BigDecimal(5));
29
-
expression.setVariable("d", new BigDecimal(5));
30
-
Assert.assertEquals(20, expression.eval().intValue());
31
-
}
32
-
33
-
@Test
34
-
public void testFunctionCaseInsensitive() {
35
-
36
-
Expression expression = new Expression("a+testsum(1,3)");
37
-
expression.setVariable("A", new BigDecimal(1));
38
-
expression.addFunction(expression.new Function("testSum", -1) {
39
-
@Override
40
-
public BigDecimal eval(List<BigDecimal> parameters) {
41
-
BigDecimal value = null;
42
-
for (BigDecimal d : parameters) {
43
-
value = value == null ? d : value.add(d);
44
-
}
45
-
return value;
46
-
}
47
-
});
48
-
49
-
Assert.assertEquals(expression.eval(), new BigDecimal(5));
50
-
51
-
}
52
-
}
-51
src/test/java/com/udojava/evalex/TestConstants.java
-51
src/test/java/com/udojava/evalex/TestConstants.java
···
1
-
package com.udojava.evalex;
2
-
3
-
import org.junit.Test;
4
-
import org.junit.runner.RunWith;
5
-
import org.junit.runners.Parameterized;
6
-
import org.junit.runners.Parameterized.Parameters;
7
-
8
-
import java.math.BigDecimal;
9
-
import java.math.MathContext;
10
-
import java.util.Arrays;
11
-
import java.util.Collection;
12
-
13
-
import static org.junit.Assert.assertEquals;
14
-
import static org.junit.Assert.assertTrue;
15
-
16
-
@RunWith(Parameterized.class)
17
-
public class TestConstants {
18
-
private final String constantName;
19
-
private final BigDecimal constantValue;
20
-
21
-
public TestConstants(String constantName, BigDecimal value) {
22
-
this.constantName = constantName;
23
-
this.constantValue = value;
24
-
}
25
-
26
-
@Parameters
27
-
public static Collection<Object[]> data() {
28
-
return Arrays.asList(
29
-
new Object[]{"e", Expression.e},
30
-
new Object[]{"PI", Expression.PI},
31
-
new Object[]{"TRUE", BigDecimal.ONE},
32
-
new Object[]{"FALSE", BigDecimal.ZERO},
33
-
new Object[]{"NULL", null}
34
-
);
35
-
}
36
-
37
-
@Test
38
-
public void testEvaluateConstants() {
39
-
assertEquals(
40
-
constantValue,
41
-
new Expression(constantName)
42
-
.setPrecision(MathContext.UNLIMITED.getPrecision())
43
-
.eval()
44
-
);
45
-
}
46
-
47
-
@Test
48
-
public void testConstantsAreNotVars() {
49
-
assertTrue(new Expression(constantName).getUsedVariables().isEmpty());
50
-
}
51
-
}
-32
src/test/java/com/udojava/evalex/TestContinuousUnary.java
-32
src/test/java/com/udojava/evalex/TestContinuousUnary.java
···
1
-
package com.udojava.evalex;
2
-
3
-
import org.junit.Assert;
4
-
import org.junit.Test;
5
-
6
-
public class TestContinuousUnary {
7
-
8
-
@Test
9
-
public void testContinuousUnary() {
10
-
11
-
Expression expression = new Expression("++1");
12
-
Assert.assertEquals(1, expression.eval().intValue());
13
-
14
-
expression = new Expression("--1");
15
-
Assert.assertEquals(1, expression.eval().intValue());
16
-
17
-
expression = new Expression("+-1");
18
-
Assert.assertEquals(-1, expression.eval().intValue());
19
-
20
-
expression = new Expression("-+1");
21
-
Assert.assertEquals(-1, expression.eval().intValue());
22
-
23
-
expression = new Expression("1-+1");
24
-
Assert.assertEquals(0, expression.eval().intValue());
25
-
26
-
expression = new Expression("-+---+++--++-1");
27
-
Assert.assertEquals(-1, expression.eval().intValue());
28
-
29
-
expression = new Expression("1--++++---2+-+----1");
30
-
Assert.assertEquals(-2, expression.eval().intValue());
31
-
}
32
-
}
-296
src/test/java/com/udojava/evalex/TestCustoms.java
-296
src/test/java/com/udojava/evalex/TestCustoms.java
···
1
-
package com.udojava.evalex;
2
-
3
-
import static org.junit.Assert.assertEquals;
4
-
5
-
import com.udojava.evalex.Expression.LazyNumber;
6
-
import java.math.BigDecimal;
7
-
import java.math.MathContext;
8
-
import java.util.List;
9
-
import org.junit.Test;
10
-
11
-
public class TestCustoms {
12
-
13
-
@Test
14
-
public void testCustomOperator() {
15
-
Expression e = new Expression("2.1234 >> 2");
16
-
17
-
e.addOperator(e.new Operator(">>", 30, true) {
18
-
@Override
19
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
20
-
return v1.movePointRight(v2.toBigInteger().intValue());
21
-
}
22
-
});
23
-
24
-
assertEquals("212.34", e.eval().toPlainString());
25
-
}
26
-
27
-
@Test
28
-
public void testCustomFunction() {
29
-
Expression e = new Expression("2 * average(12,4,8)");
30
-
e.addFunction(new AbstractFunction("average", 3) {
31
-
@Override
32
-
public BigDecimal eval(List<BigDecimal> parameters) {
33
-
BigDecimal sum = parameters.get(0).add(parameters.get(1)).add(parameters.get(2));
34
-
return sum.divide(new BigDecimal(3), MathContext.DECIMAL32);
35
-
}
36
-
});
37
-
38
-
assertEquals("16", e.eval().toPlainString());
39
-
}
40
-
41
-
@Test
42
-
public void testCustomFunctionInstanceClass() {
43
-
Expression e = new Expression("2 * average(12,4,8)");
44
-
e.addFunction(e.new Function("average", 3) {
45
-
@Override
46
-
public BigDecimal eval(List<BigDecimal> parameters) {
47
-
BigDecimal sum = parameters.get(0).add(parameters.get(1)).add(parameters.get(2));
48
-
return sum.divide(new BigDecimal(3), MathContext.DECIMAL32);
49
-
}
50
-
});
51
-
52
-
assertEquals("16", e.eval().toPlainString());
53
-
}
54
-
55
-
@Test
56
-
public void testCustomFunctionVariableParameters() {
57
-
Expression e = new Expression("2 * average(12,4,8,2,9)");
58
-
e.addFunction(new AbstractFunction("average", -1) {
59
-
@Override
60
-
public BigDecimal eval(List<BigDecimal> parameters) {
61
-
BigDecimal sum = new BigDecimal(0);
62
-
for (BigDecimal parameter : parameters) {
63
-
sum = sum.add(parameter);
64
-
}
65
-
return sum.divide(new BigDecimal(parameters.size()), MathContext.DECIMAL32);
66
-
}
67
-
});
68
-
69
-
assertEquals("14", e.eval().toPlainString());
70
-
}
71
-
72
-
@Test
73
-
public void testCustomFunctionVariableParametersInstanceClass() {
74
-
Expression e = new Expression("2 * average(12,4,8,2,9)");
75
-
e.addFunction(e.new Function("average", -1) {
76
-
@Override
77
-
public BigDecimal eval(List<BigDecimal> parameters) {
78
-
BigDecimal sum = new BigDecimal(0);
79
-
for (BigDecimal parameter : parameters) {
80
-
sum = sum.add(parameter);
81
-
}
82
-
return sum.divide(new BigDecimal(parameters.size()), MathContext.DECIMAL32);
83
-
}
84
-
});
85
-
86
-
assertEquals("14", e.eval().toPlainString());
87
-
}
88
-
89
-
@Test
90
-
public void testCustomFunctionStringParameters() {
91
-
Expression e = new Expression("STREQ(\"test\", \"test2\")");
92
-
e.addLazyFunction(e.new LazyFunction("STREQ", 2) {
93
-
private LazyNumber ZERO = new LazyNumber() {
94
-
public BigDecimal eval() {
95
-
return BigDecimal.ZERO;
96
-
}
97
-
98
-
public String getString() {
99
-
return "0";
100
-
}
101
-
};
102
-
103
-
private LazyNumber ONE = new LazyNumber() {
104
-
public BigDecimal eval() {
105
-
return BigDecimal.ONE;
106
-
}
107
-
108
-
public String getString() {
109
-
return null;
110
-
}
111
-
};
112
-
113
-
@Override
114
-
public LazyNumber lazyEval(List<LazyNumber> lazyParams) {
115
-
if (lazyParams.get(0).getString().equals(lazyParams.get(1).getString())) {
116
-
return ONE;
117
-
}
118
-
return ZERO;
119
-
}
120
-
});
121
-
122
-
assertEquals("0", e.eval().toPlainString());
123
-
}
124
-
125
-
@Test
126
-
public void testCustomFunctionStringParametersInstanceClass() {
127
-
Expression e = new Expression("STREQ(\"test\", \"test2\")");
128
-
e.addLazyFunction(e.new LazyFunction("STREQ", 2) {
129
-
private LazyNumber ZERO = new LazyNumber() {
130
-
public BigDecimal eval() {
131
-
return BigDecimal.ZERO;
132
-
}
133
-
134
-
public String getString() {
135
-
return "0";
136
-
}
137
-
};
138
-
139
-
private LazyNumber ONE = new LazyNumber() {
140
-
public BigDecimal eval() {
141
-
return BigDecimal.ONE;
142
-
}
143
-
144
-
public String getString() {
145
-
return null;
146
-
}
147
-
};
148
-
149
-
@Override
150
-
public LazyNumber lazyEval(List<LazyNumber> lazyParams) {
151
-
if (lazyParams.get(0).getString().equals(lazyParams.get(1).getString())) {
152
-
return ONE;
153
-
}
154
-
return ZERO;
155
-
}
156
-
});
157
-
158
-
assertEquals("0", e.eval().toPlainString());
159
-
}
160
-
161
-
@Test
162
-
public void testCustomFunctionBoolean() {
163
-
AbstractLazyFunction stringCompare = new AbstractLazyFunction("STREQ", 2, true) {
164
-
private LazyNumber ZERO = new LazyNumber() {
165
-
public BigDecimal eval() {
166
-
return BigDecimal.ZERO;
167
-
}
168
-
169
-
public String getString() {
170
-
return "0";
171
-
}
172
-
};
173
-
174
-
private LazyNumber ONE = new LazyNumber() {
175
-
public BigDecimal eval() {
176
-
return BigDecimal.ONE;
177
-
}
178
-
179
-
public String getString() {
180
-
return null;
181
-
}
182
-
};
183
-
184
-
@Override
185
-
public LazyNumber lazyEval(List<LazyNumber> lazyParams) {
186
-
if (lazyParams.get(0).getString().equals(lazyParams.get(1).getString())) {
187
-
return ONE;
188
-
}
189
-
return ZERO;
190
-
}
191
-
};
192
-
193
-
Expression e1 = new Expression("STREQ(\"test\", \"test\")");
194
-
e1.addLazyFunction(stringCompare);
195
-
assertEquals("1", e1.eval().toPlainString());
196
-
197
-
Expression e2 = new Expression("STREQ(\"test\", \"test2\")");
198
-
e2.addLazyFunction(stringCompare);
199
-
assertEquals("0", e2.eval().toPlainString());
200
-
}
201
-
202
-
@Test
203
-
public void testUnary() {
204
-
Expression exp = new Expression("~23");
205
-
206
-
exp.addOperator(new AbstractUnaryOperator("~", 60, false) {
207
-
@Override
208
-
public BigDecimal evalUnary(BigDecimal bigDecimal) {
209
-
return bigDecimal;
210
-
}
211
-
});
212
-
assertEquals("23", exp.eval().toPlainString());
213
-
}
214
-
215
-
@Test
216
-
public void testUnaryPostfix() {
217
-
Expression exp = new Expression("1+4!");
218
-
219
-
exp.addOperator(new AbstractOperator("!", 61, true, false, true) {
220
-
@Override
221
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
222
-
if (!v1.remainder(BigDecimal.ONE).equals(BigDecimal.ZERO)) {
223
-
throw new ArithmeticException("Operand must be an integer");
224
-
}
225
-
BigDecimal factorial = v1;
226
-
v1 = v1.subtract(BigDecimal.ONE);
227
-
if (factorial.compareTo(BigDecimal.ZERO) == 0 || factorial.compareTo(BigDecimal.ONE) == 0) {
228
-
return BigDecimal.ONE;
229
-
} else {
230
-
while (v1.compareTo(BigDecimal.ONE) > 0) {
231
-
factorial = factorial.multiply(v1);
232
-
v1 = v1.subtract(BigDecimal.ONE);
233
-
}
234
-
return factorial;
235
-
}
236
-
}
237
-
});
238
-
assertEquals("25", exp.eval().toPlainString());
239
-
}
240
-
241
-
@Test
242
-
public void testCustomOperatorAnd() {
243
-
Expression e = new Expression("1 == 1 AND 2 == 2");
244
-
245
-
e.addOperator(e.new Operator("AND", Expression.OPERATOR_PRECEDENCE_AND, false, true) {
246
-
@Override
247
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
248
-
boolean b1 = v1.compareTo(BigDecimal.ZERO) != 0;
249
-
250
-
if (!b1) {
251
-
return BigDecimal.ZERO;
252
-
}
253
-
254
-
boolean b2 = v2.compareTo(BigDecimal.ZERO) != 0;
255
-
return b2 ? BigDecimal.ONE : BigDecimal.ZERO;
256
-
}
257
-
});
258
-
259
-
assertEquals("1", e.eval().toPlainString());
260
-
}
261
-
262
-
@Test
263
-
public void testCustomFunctionWithStringParams() {
264
-
Expression e = new Expression("INDEXOF(STRING1, STRING2)");
265
-
e.addLazyFunction(new AbstractLazyFunction("INDEXOF", 2) {
266
-
@Override
267
-
public LazyNumber lazyEval(List<LazyNumber> lazyParams) {
268
-
String st1 = lazyParams.get(0).getString();
269
-
String st2 = lazyParams.get(1).getString();
270
-
final int index = st1.indexOf(st2);
271
-
272
-
return new LazyNumber() {
273
-
@Override
274
-
public BigDecimal eval() {
275
-
return new BigDecimal(index);
276
-
}
277
-
278
-
@Override
279
-
public String getString() {
280
-
return Integer.toString(index);
281
-
}
282
-
};
283
-
}
284
-
});
285
-
286
-
e.setVariable("STRING1", "The quick brown fox");
287
-
e.setVariable("STRING2", "The");
288
-
289
-
assertEquals("0", e.eval().toPlainString());
290
-
291
-
e.setVariable("STRING1", "The quick brown fox");
292
-
e.setVariable("STRING2", "brown");
293
-
294
-
assertEquals("10", e.eval().toPlainString());
295
-
}
296
-
}
-572
src/test/java/com/udojava/evalex/TestEval.java
-572
src/test/java/com/udojava/evalex/TestEval.java
···
1
-
package com.udojava.evalex;
2
-
3
-
import static org.junit.Assert.assertEquals;
4
-
import static org.junit.Assert.assertNotSame;
5
-
import static org.junit.Assert.assertNull;
6
-
7
-
import com.udojava.evalex.Expression.ExpressionException;
8
-
import java.math.BigDecimal;
9
-
import java.math.MathContext;
10
-
import java.math.RoundingMode;
11
-
import org.junit.Test;
12
-
13
-
14
-
public class TestEval {
15
-
16
-
@Test
17
-
public void testsinAB() {
18
-
String err = "";
19
-
try {
20
-
Expression expression = new Expression("sin(a+x)");
21
-
expression.eval();
22
-
} catch (ExpressionException e) {
23
-
err = e.getMessage();
24
-
}
25
-
26
-
assertEquals("Unknown operator or function: a", err);
27
-
}
28
-
29
-
@Test
30
-
public void testInvalidExpressions1() {
31
-
String err = "";
32
-
try {
33
-
Expression expression = new Expression("12 18 2");
34
-
expression.eval();
35
-
} catch (ExpressionException e) {
36
-
err = e.getMessage();
37
-
}
38
-
39
-
assertEquals("Missing operator at character position 3", err);
40
-
}
41
-
42
-
@Test
43
-
public void testInvalidExpressions3() {
44
-
String err = "";
45
-
try {
46
-
Expression expression = new Expression("12 + * 18");
47
-
expression.eval();
48
-
} catch (ExpressionException e) {
49
-
err = e.getMessage();
50
-
}
51
-
52
-
assertEquals("Unknown unary operator * at character position 6", err);
53
-
}
54
-
55
-
@Test
56
-
public void testInvalidExpressions4() {
57
-
String err = "";
58
-
try {
59
-
Expression expression = new Expression("");
60
-
expression.eval();
61
-
} catch (ExpressionException e) {
62
-
err = e.getMessage();
63
-
}
64
-
65
-
assertEquals("Empty expression", err);
66
-
}
67
-
68
-
@Test
69
-
public void testInvalidExpressions5() {
70
-
String err = "";
71
-
try {
72
-
Expression expression = new Expression("1 1+2/");
73
-
expression.toRPN();
74
-
expression.eval();
75
-
} catch (ExpressionException e) {
76
-
err = e.getMessage();
77
-
}
78
-
79
-
assertEquals("Missing operator at character position 2", err);
80
-
}
81
-
82
-
@Test
83
-
public void testBrackets() {
84
-
assertEquals("3", new Expression("(1+2)").eval().toPlainString());
85
-
assertEquals("3", new Expression("((1+2))").eval().toPlainString());
86
-
assertEquals("3", new Expression("(((1+2)))").eval().toPlainString());
87
-
assertEquals("9", new Expression("(1+2)*(1+2)").eval().toPlainString());
88
-
assertEquals("10", new Expression("(1+2)*(1+2)+1").eval().toPlainString());
89
-
assertEquals("12", new Expression("(1+2)*((1+2)+1)").eval().toPlainString());
90
-
}
91
-
92
-
@Test(expected = RuntimeException.class)
93
-
public void testUnknow1() {
94
-
assertEquals("", new Expression("7#9").eval().toPlainString());
95
-
}
96
-
97
-
@Test(expected = RuntimeException.class)
98
-
public void testUnknow2() {
99
-
assertEquals("", new Expression("123.6*-9.8-7#9").eval().toPlainString());
100
-
}
101
-
102
-
@Test
103
-
public void testSimple() {
104
-
assertEquals("3", new Expression("1+2").eval().toPlainString());
105
-
assertEquals("2", new Expression("4/2").eval().toPlainString());
106
-
assertEquals("5", new Expression("3+4/2").eval().toPlainString());
107
-
assertEquals("3.5", new Expression("(3+4)/2").eval().toPlainString());
108
-
assertEquals("7.98", new Expression("4.2*1.9").eval().toPlainString());
109
-
assertEquals("2", new Expression("8%3").eval().toPlainString());
110
-
assertEquals("0", new Expression("8%2").eval().toPlainString());
111
-
assertEquals("0.2", new Expression("2*.1").eval().toPlainString());
112
-
}
113
-
114
-
@Test
115
-
public void testUnaryMinus() {
116
-
assertEquals("-3", new Expression("-3").eval().toPlainString());
117
-
assertEquals("-2", new Expression("-SQRT(4)").eval().toPlainString());
118
-
assertEquals("-12", new Expression("-(5*3+(+10-13))").eval().toPlainString());
119
-
assertEquals("-2.75", new Expression("-2+3/4*-1").eval().toPlainString());
120
-
assertEquals("9", new Expression("-3^2").eval().toPlainString());
121
-
assertEquals("0.5", new Expression("4^-0.5").eval().toPlainString());
122
-
assertEquals("-1.25", new Expression("-2+3/4").eval().toPlainString());
123
-
assertEquals("-1", new Expression("-(3+-4*-1/-2)").eval().toPlainString());
124
-
assertEquals("1.8", new Expression("2+-.2").eval().toPlainString());
125
-
}
126
-
127
-
@Test
128
-
public void testUnaryPlus() {
129
-
assertEquals("3", new Expression("+3").eval().toPlainString());
130
-
assertEquals("4", new Expression("+(3-1+2)").eval().toPlainString());
131
-
assertEquals("4", new Expression("+(3-(+1)+2)").eval().toPlainString());
132
-
assertEquals("9", new Expression("+3^2").eval().toPlainString());
133
-
}
134
-
135
-
@Test
136
-
public void testPow() {
137
-
assertEquals("16", new Expression("2^4").eval().toPlainString());
138
-
assertEquals("256", new Expression("2^8").eval().toPlainString());
139
-
assertEquals("9", new Expression("3^2").eval().toPlainString());
140
-
assertEquals("6.25", new Expression("2.5^2").eval().toPlainString());
141
-
assertEquals("28.34045", new Expression("2.6^3.5").eval().toPlainString());
142
-
}
143
-
144
-
@Test
145
-
public void testSqrt() {
146
-
assertEquals("4", new Expression("SQRT(16)").eval().toPlainString());
147
-
assertEquals("1.4142135", new Expression("SQRT(2)").eval().toPlainString());
148
-
assertEquals(
149
-
"1.41421356237309504880168872420969807856967187537694807317667973799073247846210703885038753432764157273501384623091229702492483605",
150
-
new Expression("SQRT(2)").setPrecision(128).eval().toPlainString());
151
-
assertEquals("2.2360679", new Expression("SQRT(5)").eval().toPlainString());
152
-
assertEquals("99.3730345", new Expression("SQRT(9875)").eval().toPlainString());
153
-
assertEquals("2.3558437", new Expression("SQRT(5.55)").eval().toPlainString());
154
-
assertEquals("0", new Expression("SQRT(0)").eval().toPlainString());
155
-
}
156
-
157
-
@Test
158
-
public void testFunctions() {
159
-
assertNotSame("1.5", new Expression("Random()").eval().toPlainString());
160
-
assertEquals("0.400349", new Expression("SIN(23.6)").eval().toPlainString());
161
-
assertEquals("8", new Expression("MAX(-7,8)").eval().toPlainString());
162
-
assertEquals("5", new Expression("MAX(3,max(4,5))").eval().toPlainString());
163
-
assertEquals("9.6",
164
-
new Expression("MAX(3,max(MAX(9.6,-4.2),Min(5,9)))").eval().toPlainString());
165
-
assertEquals("2.302585", new Expression("LOG(10)").eval().toPlainString());
166
-
}
167
-
168
-
@Test
169
-
public void testExpectedParameterNumbers() {
170
-
String err = "";
171
-
try {
172
-
Expression expression = new Expression("Random(1)");
173
-
expression.eval();
174
-
} catch (ExpressionException e) {
175
-
err = e.getMessage();
176
-
}
177
-
assertEquals("Function Random expected 0 parameters, got 1", err);
178
-
179
-
try {
180
-
Expression expression = new Expression("SIN(1, 6)");
181
-
expression.eval();
182
-
} catch (ExpressionException e) {
183
-
err = e.getMessage();
184
-
}
185
-
assertEquals("Function SIN expected 1 parameters, got 2", err);
186
-
}
187
-
188
-
@Test
189
-
public void testVariableParameterNumbers() {
190
-
String err = "";
191
-
try {
192
-
Expression expression = new Expression("min()");
193
-
expression.eval();
194
-
} catch (ExpressionException e) {
195
-
err = e.getMessage();
196
-
}
197
-
assertEquals("MIN requires at least one parameter", err);
198
-
199
-
assertEquals("1", new Expression("min(1)").eval().toPlainString());
200
-
assertEquals("1", new Expression("min(1, 2)").eval().toPlainString());
201
-
assertEquals("1", new Expression("min(1, 2, 3)").eval().toPlainString());
202
-
assertEquals("3", new Expression("max(3, 2, 1)").eval().toPlainString());
203
-
assertEquals("9", new Expression("max(3, 2, 1, 4, 5, 6, 7, 8, 9, 0)").eval().toPlainString());
204
-
}
205
-
206
-
@Test
207
-
public void testOrphanedOperators() {
208
-
String err = "";
209
-
try {
210
-
new Expression("/").eval();
211
-
} catch (ExpressionException e) {
212
-
err = e.getMessage();
213
-
}
214
-
assertEquals("Unknown unary operator / at character position 1", err);
215
-
216
-
err = "";
217
-
try {
218
-
new Expression("3/").eval();
219
-
} catch (ExpressionException e) {
220
-
err = e.getMessage();
221
-
}
222
-
assertEquals("Missing parameter(s) for operator /", err);
223
-
224
-
err = "";
225
-
try {
226
-
new Expression("/3").eval();
227
-
} catch (ExpressionException e) {
228
-
err = e.getMessage();
229
-
}
230
-
assertEquals("Unknown unary operator / at character position 1", err);
231
-
232
-
err = "";
233
-
try {
234
-
new Expression("SIN(MAX(23,45,12))/").eval();
235
-
} catch (ExpressionException e) {
236
-
err = e.getMessage();
237
-
}
238
-
assertEquals("Missing parameter(s) for operator /", err);
239
-
240
-
}
241
-
242
-
@Test(expected = ExpressionException.class)
243
-
public void closeParenAtStartCausesExpressionException() {
244
-
new Expression("(").eval();
245
-
}
246
-
247
-
@Test
248
-
public void testOrphanedOperatorsInFunctionParameters() {
249
-
String err = "";
250
-
try {
251
-
new Expression("min(/)").eval();
252
-
} catch (ExpressionException e) {
253
-
err = e.getMessage();
254
-
}
255
-
assertEquals("Unknown unary operator / at character position 5", err);
256
-
257
-
err = "";
258
-
try {
259
-
new Expression("min(3/)").eval();
260
-
} catch (ExpressionException e) {
261
-
err = e.getMessage();
262
-
}
263
-
assertEquals("Missing parameter(s) for operator / at character position 5", err);
264
-
265
-
err = "";
266
-
try {
267
-
new Expression("min(/3)").eval();
268
-
} catch (ExpressionException e) {
269
-
err = e.getMessage();
270
-
}
271
-
assertEquals("Unknown unary operator / at character position 5", err);
272
-
273
-
err = "";
274
-
try {
275
-
new Expression("SIN(MAX(23,45,12,23.6/))").eval();
276
-
} catch (ExpressionException e) {
277
-
err = e.getMessage();
278
-
}
279
-
assertEquals("Missing parameter(s) for operator / at character position 21", err);
280
-
281
-
err = "";
282
-
try {
283
-
new Expression("SIN(MAX(23,45,12/,23.6))").eval();
284
-
} catch (ExpressionException e) {
285
-
err = e.getMessage();
286
-
}
287
-
assertEquals("Missing parameter(s) for operator / at character position 16", err);
288
-
289
-
err = "";
290
-
try {
291
-
new Expression("SIN(MAX(23,45,>=12,23.6))").eval();
292
-
} catch (ExpressionException e) {
293
-
err = e.getMessage();
294
-
}
295
-
assertEquals("Unknown unary operator >= at character position 15", err);
296
-
297
-
err = "";
298
-
try {
299
-
new Expression("SIN(MAX(>=23,45,12,23.6))").eval();
300
-
} catch (ExpressionException e) {
301
-
err = e.getMessage();
302
-
}
303
-
assertEquals("Unknown unary operator >= at character position 9", err);
304
-
}
305
-
306
-
@Test
307
-
public void testExtremeFunctionNesting() {
308
-
assertNotSame("1.5", new Expression("Random()").eval().toPlainString());
309
-
assertEquals("0.0002791281", new Expression("SIN(SIN(COS(23.6)))").eval().toPlainString());
310
-
assertEquals("-4",
311
-
new Expression("MIN(0, SIN(SIN(COS(23.6))), 0-MAX(3,4,MAX(0,SIN(1))), 10)").eval()
312
-
.toPlainString());
313
-
}
314
-
315
-
@Test
316
-
public void testTrigonometry() {
317
-
assertEquals("0.5", new Expression("SIN(30)").eval().toPlainString());
318
-
assertEquals("0.8660254", new Expression("cos(30)").eval().toPlainString());
319
-
assertEquals("0.5773503", new Expression("TAN(30)").eval().toPlainString());
320
-
assertEquals("5343237000000", new Expression("SINH(30)").eval().toPlainString());
321
-
assertEquals("5343237000000", new Expression("COSH(30)").eval().toPlainString());
322
-
assertEquals("1", new Expression("TANH(30)").eval().toPlainString());
323
-
assertEquals("0.5235988", new Expression("RAD(30)").eval().toPlainString());
324
-
assertEquals("1718.873", new Expression("DEG(30)").eval().toPlainString());
325
-
assertEquals("30", new Expression("atan(0.5773503)").eval().toPlainString());
326
-
assertEquals("30", new Expression("atan2(0.5773503, 1)").eval().toPlainString());
327
-
assertEquals("33.69007", new Expression("atan2(2, 3)").eval().toPlainString());
328
-
assertEquals("146.3099", new Expression("atan2(2, -3)").eval().toPlainString());
329
-
assertEquals("-146.3099", new Expression("atan2(-2, -3)").eval().toPlainString());
330
-
assertEquals("-33.69007", new Expression("atan2(-2, 3)").eval().toPlainString());
331
-
assertEquals("1.154701", new Expression("SEC(30)").eval().toPlainString());
332
-
assertEquals("1.414214", new Expression("SEC(45)").eval().toPlainString());
333
-
assertEquals("2", new Expression("SEC(60)").eval().toPlainString());
334
-
assertEquals("3.863703", new Expression("SEC(75)").eval().toPlainString());
335
-
assertEquals("2", new Expression("CSC(30)").eval().toPlainString());
336
-
assertEquals("1.414214", new Expression("CSC(45)").eval().toPlainString());
337
-
assertEquals("1.154701", new Expression("CSC(60)").eval().toPlainString());
338
-
assertEquals("1.035276", new Expression("CSC(75)").eval().toPlainString());
339
-
assertEquals("0.0000000000001871525", new Expression("SECH(30)").eval().toPlainString());
340
-
assertEquals("0.00000000000000000005725037", new Expression("SECH(45)").eval().toPlainString());
341
-
assertEquals("0.00000000000000000000000001751302",
342
-
new Expression("SECH(60)").eval().toPlainString());
343
-
assertEquals("0.000000000000000000000000000000005357274",
344
-
new Expression("SECH(75)").eval().toPlainString());
345
-
assertEquals("0.0000000000001871525", new Expression("CSCH(30)").eval().toPlainString());
346
-
assertEquals("0.00000000000000000005725037", new Expression("CSCH(45)").eval().toPlainString());
347
-
assertEquals("0.00000000000000000000000001751302",
348
-
new Expression("CSCH(60)").eval().toPlainString());
349
-
assertEquals("0.000000000000000000000000000000005357274",
350
-
new Expression("CSCH(75)").eval().toPlainString());
351
-
assertEquals("1.732051", new Expression("COT(30)").eval().toPlainString());
352
-
assertEquals("1", new Expression("COT(45)").eval().toPlainString());
353
-
assertEquals("0.5773503", new Expression("COT(60)").eval().toPlainString());
354
-
assertEquals("0.2679492", new Expression("COT(75)").eval().toPlainString());
355
-
assertEquals("1.909152", new Expression("ACOT(30)").eval().toPlainString());
356
-
assertEquals("1.27303", new Expression("ACOT(45)").eval().toPlainString());
357
-
assertEquals("0.9548413", new Expression("ACOT(60)").eval().toPlainString());
358
-
assertEquals("0.7638985", new Expression("ACOT(75)").eval().toPlainString());
359
-
assertEquals("1", new Expression("COTH(30)").eval().toPlainString());
360
-
assertEquals("1.199538", new Expression("COTH(1.2)").eval().toPlainString());
361
-
assertEquals("1.016596", new Expression("COTH(2.4)").eval().toPlainString());
362
-
assertEquals("4.094622", new Expression("ASINH(30)").eval().toPlainString());
363
-
assertEquals("4.499933", new Expression("ASINH(45)").eval().toPlainString());
364
-
assertEquals("4.787561", new Expression("ASINH(60)").eval().toPlainString());
365
-
assertEquals("5.01068", new Expression("ASINH(75)").eval().toPlainString());
366
-
assertEquals("0", new Expression("ACOSH(1)").eval().toPlainString());
367
-
assertEquals("4.094067", new Expression("ACOSH(30)").eval().toPlainString());
368
-
assertEquals("4.499686", new Expression("ACOSH(45)").eval().toPlainString());
369
-
assertEquals("4.787422", new Expression("ACOSH(60)").eval().toPlainString());
370
-
assertEquals("5.010591", new Expression("ACOSH(75)").eval().toPlainString());
371
-
assertEquals("0", new Expression("ATANH(0)").eval().toPlainString());
372
-
assertEquals("0.5493061", new Expression("ATANH(0.5)").eval().toPlainString());
373
-
assertEquals("-0.5493061", new Expression("ATANH(-0.5)").eval().toPlainString());
374
-
}
375
-
376
-
@Test
377
-
public void testMinMaxAbs() {
378
-
assertEquals("3.78787", new Expression("MAX(3.78787,3.78786)").eval().toPlainString());
379
-
assertEquals("3.78787", new Expression("max(3.78786,3.78787)").eval().toPlainString());
380
-
assertEquals("3.78786", new Expression("MIN(3.78787,3.78786)").eval().toPlainString());
381
-
assertEquals("3.78786", new Expression("Min(3.78786,3.78787)").eval().toPlainString());
382
-
assertEquals("2.123", new Expression("aBs(-2.123)").eval().toPlainString());
383
-
assertEquals("2.123", new Expression("abs(2.123)").eval().toPlainString());
384
-
}
385
-
386
-
@Test
387
-
public void testRounding() {
388
-
assertEquals("3.8", new Expression("round(3.78787,1)").eval().toPlainString());
389
-
assertEquals("3.788", new Expression("round(3.78787,3)").eval().toPlainString());
390
-
assertEquals("3.734", new Expression("round(3.7345,3)").eval().toPlainString());
391
-
assertEquals("-3.734", new Expression("round(-3.7345,3)").eval().toPlainString());
392
-
assertEquals("-3.79", new Expression("round(-3.78787,2)").eval().toPlainString());
393
-
assertEquals("123.79", new Expression("round(123.78787,2)").eval().toPlainString());
394
-
assertEquals("3", new Expression("floor(3.78787)").eval().toPlainString());
395
-
assertEquals("4", new Expression("ceiling(3.78787)").eval().toPlainString());
396
-
assertEquals("-3", new Expression("floor(-2.1)").eval().toPlainString());
397
-
assertEquals("-2", new Expression("ceiling(-2.1)").eval().toPlainString());
398
-
}
399
-
400
-
@Test
401
-
public void testMathContext() {
402
-
Expression e = new Expression("2.5/3").setPrecision(2);
403
-
assertEquals("0.83", e.eval().toPlainString());
404
-
405
-
e = new Expression("2.5/3").setPrecision(3);
406
-
assertEquals("0.833", e.eval().toPlainString());
407
-
408
-
e = new Expression("2.5/3").setPrecision(8);
409
-
assertEquals("0.83333333", e.eval().toPlainString());
410
-
411
-
e = new Expression("2.5/3").setRoundingMode(RoundingMode.DOWN);
412
-
assertEquals("0.8333333", e.eval().toPlainString());
413
-
414
-
e = new Expression("2.5/3").setRoundingMode(RoundingMode.UP);
415
-
assertEquals("0.8333334", e.eval().toPlainString());
416
-
}
417
-
418
-
@Test
419
-
public void unknownFunctionsFailGracefully() {
420
-
String err = "";
421
-
try {
422
-
new Expression("unk(1,2,3)").eval();
423
-
} catch (ExpressionException e) {
424
-
err = e.getMessage();
425
-
}
426
-
427
-
assertEquals("Unknown function unk at character position 1", err);
428
-
}
429
-
430
-
@Test
431
-
public void unknownOperatorsFailGracefully() {
432
-
String err = "";
433
-
try {
434
-
new Expression("a |*| b").eval();
435
-
} catch (ExpressionException e) {
436
-
err = e.getMessage();
437
-
}
438
-
439
-
assertEquals("Unknown operator |*| at character position 3", err);
440
-
}
441
-
442
-
@Test
443
-
public void testNull() {
444
-
Expression e = new Expression("null");
445
-
assertNull(e.eval());
446
-
}
447
-
448
-
@Test
449
-
public void testCalculationWithNull() {
450
-
String err = "";
451
-
try {
452
-
new Expression("null+1").eval();
453
-
} catch (ArithmeticException e) {
454
-
err = e.getMessage();
455
-
}
456
-
assertEquals("First operand may not be null", err);
457
-
458
-
err = "";
459
-
try {
460
-
new Expression("1 + NULL").eval();
461
-
} catch (ArithmeticException e) {
462
-
err = e.getMessage();
463
-
}
464
-
assertEquals("Second operand may not be null", err);
465
-
466
-
err = "";
467
-
try {
468
-
new Expression("round(Null, 1)").eval();
469
-
} catch (ArithmeticException e) {
470
-
err = e.getMessage();
471
-
}
472
-
assertEquals("First operand may not be null", err);
473
-
474
-
err = "";
475
-
try {
476
-
new Expression("round(1, NulL)").eval();
477
-
} catch (ArithmeticException e) {
478
-
err = e.getMessage();
479
-
}
480
-
assertEquals("Second operand may not be null", err);
481
-
}
482
-
483
-
@Test
484
-
public void canEvalHexExpression() {
485
-
BigDecimal result = new Expression("0xcafe").eval();
486
-
assertEquals("51966", result.toPlainString());
487
-
}
488
-
489
-
@Test
490
-
public void hexExpressionCanUseUpperCaseCharacters() {
491
-
BigDecimal result = new Expression("0XCAFE").eval();
492
-
assertEquals("51966", result.toPlainString());
493
-
}
494
-
495
-
@Test
496
-
public void hexMinus() {
497
-
BigDecimal result = new Expression("-0XCAFE").eval();
498
-
assertEquals("-51966", result.toPlainString());
499
-
}
500
-
501
-
@Test
502
-
public void hexMinusBlanks() {
503
-
BigDecimal result = new Expression(" 0xa + -0XCAFE ").eval();
504
-
assertEquals("-51956", result.toPlainString());
505
-
}
506
-
507
-
@Test
508
-
public void longHexExpressionWorks() {
509
-
BigDecimal result = new Expression("0xcafebabe", MathContext.DECIMAL128).eval();
510
-
assertEquals("3405691582", result.toPlainString());
511
-
}
512
-
513
-
@Test(expected = ExpressionException.class)
514
-
public void hexExpressionDoesNotAllowNonHexCharacters() {
515
-
BigDecimal result = new Expression("0xbaby").eval();
516
-
}
517
-
518
-
@Test(expected = NumberFormatException.class)
519
-
public void throwsExceptionIfDoesNotContainHexDigits() {
520
-
BigDecimal result = new Expression("0x").eval();
521
-
}
522
-
523
-
@Test
524
-
public void hexExpressionsEvaluatedAsExpected() {
525
-
BigDecimal result = new Expression("0xcafe + 0xbabe").eval();
526
-
assertEquals("99772", result.toPlainString());
527
-
}
528
-
529
-
@Test
530
-
public void testResultZeroStripping() {
531
-
Expression expression = new Expression("200.40000 / 2");
532
-
assertEquals("100.2", expression.eval().toPlainString());
533
-
assertEquals("100.2000", expression.eval(false).toPlainString());
534
-
}
535
-
536
-
@Test
537
-
public void testImplicitMultiplication() {
538
-
Expression expression = new Expression("22(3+1)");
539
-
assertEquals("88", expression.eval().toPlainString());
540
-
541
-
expression = new Expression("(a+b)(a-b)");
542
-
assertEquals("-3", expression.with("a", "1").and("b", "2").eval().toPlainString());
543
-
544
-
expression = new Expression("0xA(a+b)");
545
-
assertEquals("30", expression.with("a", "1").and("b", "2").eval().toPlainString());
546
-
}
547
-
548
-
@Test
549
-
public void testNoLeadingZero() {
550
-
Expression e = new Expression("0.1 + .1");
551
-
assertEquals("0.2", e.eval().toPlainString());
552
-
553
-
e = new Expression(".2*.3");
554
-
assertEquals("0.06", e.eval().toPlainString());
555
-
556
-
e = new Expression(".2*.3+.1");
557
-
assertEquals("0.16", e.eval().toPlainString());
558
-
}
559
-
560
-
@Test
561
-
public void testUnexpectedComma() {
562
-
String err = "";
563
-
try {
564
-
Expression expression = new Expression("2+3,8");
565
-
expression.eval();
566
-
} catch (ExpressionException e) {
567
-
err = e.getMessage();
568
-
}
569
-
assertEquals("Unexpected comma at character position 3", err);
570
-
}
571
-
}
572
-
-76
src/test/java/com/udojava/evalex/TestExposedComponents.java
-76
src/test/java/com/udojava/evalex/TestExposedComponents.java
···
1
-
package com.udojava.evalex;
2
-
3
-
import java.math.BigDecimal;
4
-
import java.util.List;
5
-
import org.junit.Assert;
6
-
import org.junit.Test;
7
-
8
-
/**
9
-
* Created by fobbyal on 2/28/2016 at 3:48 PM. Project EvalEx
10
-
*/
11
-
public class TestExposedComponents {
12
-
13
-
@Test
14
-
public void testDeclaredOperators() {
15
-
Expression expression = new Expression("c+d");
16
-
int originalOperator = expression.getDeclaredOperators().size();
17
-
expression.addOperator(expression.new Operator("$$", -1, true) {
18
-
@Override
19
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
20
-
return null;
21
-
}
22
-
});
23
-
Assert.assertTrue("Operator List should have the new $$ operator",
24
-
expression.getDeclaredOperators().contains("$$"));
25
-
Assert.assertEquals("Should have an extra operators", expression.getDeclaredOperators().size(),
26
-
originalOperator + 1);
27
-
}
28
-
29
-
@Test
30
-
public void testDeclaredVariables() {
31
-
Expression expression = new Expression("c+d");
32
-
int originalVarCounts = expression.getDeclaredVariables().size();
33
-
expression.setVariable("var1", new BigDecimal(12));
34
-
35
-
Assert.assertTrue("Variable list should have new var1 variable declared",
36
-
expression.getDeclaredVariables().contains("var1"));
37
-
Assert.assertEquals("Variable list should have q more declared variable",
38
-
expression.getDeclaredVariables().size(), originalVarCounts + 1);
39
-
}
40
-
41
-
@Test
42
-
public void testDeclaredFunctionGetter() {
43
-
Expression expression = new Expression("a+b");
44
-
int originalFunctionCount = expression.getDeclaredFunctions().size();
45
-
expression.addFunction(expression.new Function("func1", 3) {
46
-
@Override
47
-
public BigDecimal eval(List<BigDecimal> parameters) {
48
-
return null;
49
-
}
50
-
});
51
-
52
-
Assert.assertTrue("Function list should have new func1 function declared",
53
-
expression.getDeclaredFunctions().contains("FUNC1"));
54
-
Assert.assertEquals("Function list should have one more function declared",
55
-
expression.getDeclaredFunctions().size(), originalFunctionCount + 1);
56
-
}
57
-
58
-
59
-
@Test(expected = RuntimeException.class)
60
-
public void testUnmodifiableFunctionList() {
61
-
Expression expression = new Expression("c+d");
62
-
expression.getDeclaredFunctions().add("$$$");
63
-
}
64
-
65
-
@Test(expected = RuntimeException.class)
66
-
public void testUnmodifiableOperatorList() {
67
-
Expression expression = new Expression("c+d");
68
-
expression.getDeclaredOperators().add("$$$");
69
-
}
70
-
71
-
@Test(expected = RuntimeException.class)
72
-
public void testUnmodifiableVariableList() {
73
-
Expression expression = new Expression("c+d");
74
-
expression.getDeclaredVariables().add("$$$");
75
-
}
76
-
}
-21
src/test/java/com/udojava/evalex/TestFactorial.java
-21
src/test/java/com/udojava/evalex/TestFactorial.java
···
1
-
package com.udojava.evalex;
2
-
3
-
import static org.junit.Assert.assertEquals;
4
-
5
-
import org.junit.Test;
6
-
7
-
public class TestFactorial {
8
-
9
-
10
-
@Test
11
-
public void testFactorial() {
12
-
13
-
assertEquals("1", new Expression("FACT(0)").eval().toPlainString());
14
-
assertEquals("1", new Expression("FACT(1)").eval().toPlainString());
15
-
assertEquals("2", new Expression("FACT(2)").eval().toPlainString());
16
-
assertEquals("6", new Expression("FACT(3)").eval().toPlainString());
17
-
assertEquals("24", new Expression("FACT(4)").eval().toPlainString());
18
-
19
-
}
20
-
21
-
}
-45
src/test/java/com/udojava/evalex/TestGetUsedVariables.java
-45
src/test/java/com/udojava/evalex/TestGetUsedVariables.java
···
1
-
package com.udojava.evalex;
2
-
3
-
import static org.junit.Assert.assertEquals;
4
-
import static org.junit.Assert.assertTrue;
5
-
6
-
import java.math.BigDecimal;
7
-
import java.util.Collections;
8
-
import java.util.List;
9
-
import org.junit.Test;
10
-
11
-
public class TestGetUsedVariables {
12
-
13
-
@Test
14
-
public void testVars() {
15
-
Expression ex = new Expression("a/2*PI+MIN(e,b)");
16
-
List<String> usedVars = ex.getUsedVariables();
17
-
assertEquals(2, usedVars.size());
18
-
assertTrue(usedVars.contains("a"));
19
-
assertTrue(usedVars.contains("b"));
20
-
}
21
-
22
-
@Test
23
-
public void testVarsLongNames() {
24
-
Expression ex = new Expression("var1/2*PI+MIN(var2,var3)");
25
-
List<String> usedVars = ex.getUsedVariables();
26
-
assertEquals(3, usedVars.size());
27
-
assertTrue(usedVars.contains("var1"));
28
-
assertTrue(usedVars.contains("var2"));
29
-
assertTrue(usedVars.contains("var3"));
30
-
}
31
-
32
-
@Test
33
-
public void testVarsNothing() {
34
-
Expression ex = new Expression("1/2");
35
-
List<String> usedVars = ex.getUsedVariables();
36
-
assertEquals(0, usedVars.size());
37
-
}
38
-
39
-
@Test
40
-
public void testStringComparison() {
41
-
// test for issue #267 (getUsedVariables() throws NPE in expressions containing string literals)
42
-
Expression ex = new Expression("foo=\"bar\"").setVariable("foo", (BigDecimal) null);
43
-
assertEquals(Collections.singletonList("foo"), ex.getUsedVariables());
44
-
}
45
-
}
-54
src/test/java/com/udojava/evalex/TestLazyIf.java
-54
src/test/java/com/udojava/evalex/TestLazyIf.java
···
1
-
package com.udojava.evalex;
2
-
3
-
import static org.junit.Assert.assertEquals;
4
-
5
-
import java.math.BigDecimal;
6
-
import org.junit.Assert;
7
-
import org.junit.Test;
8
-
9
-
/**
10
-
* Created by showdown on 4/5/2016 at 12:19 AM. Project EvalEx
11
-
*/
12
-
public class TestLazyIf {
13
-
14
-
@Test
15
-
public void testLazyIf() {
16
-
Expression expression = new Expression("if(a=0,0,12/a)");
17
-
expression.setVariable("a", new BigDecimal(0));
18
-
Assert.assertEquals(expression.eval(), new BigDecimal(0));
19
-
}
20
-
21
-
@Test
22
-
public void testLazyIfWithNestedFunction() {
23
-
Expression expression = new Expression("if(a=0,0,abs(12/a))");
24
-
expression.setVariable("a", new BigDecimal(0));
25
-
Assert.assertEquals(expression.eval(), new BigDecimal(0));
26
-
}
27
-
28
-
@Test
29
-
public void testLazyIfWithNestedSuccessIf() {
30
-
Expression expression = new Expression("if(a=0,0,if(5/a>3,2,4))");
31
-
expression.setVariable("a", new BigDecimal(0));
32
-
Assert.assertEquals(expression.eval(), new BigDecimal(0));
33
-
}
34
-
35
-
@Test(expected = ArithmeticException.class)
36
-
public void testLazyIfWithNestedFailingIf() {
37
-
Expression expression = new Expression("if(a=0,if(5/a>3,2,4),0)");
38
-
expression.setVariable("a", new BigDecimal(0));
39
-
Assert.assertEquals(expression.eval(), new BigDecimal(0));
40
-
}
41
-
42
-
43
-
@Test
44
-
public void testLazyIfWithNull() {
45
-
String err = "";
46
-
try {
47
-
new Expression("if(a,0,12/a)").setVariable("a", "null").eval();
48
-
} catch (ArithmeticException e) {
49
-
err = e.getMessage();
50
-
}
51
-
assertEquals("Operand may not be null", err);
52
-
}
53
-
54
-
}
-104
src/test/java/com/udojava/evalex/TestNested.java
-104
src/test/java/com/udojava/evalex/TestNested.java
···
1
-
package com.udojava.evalex;
2
-
3
-
import static org.junit.Assert.assertEquals;
4
-
5
-
import java.math.BigDecimal;
6
-
import java.math.MathContext;
7
-
import java.math.RoundingMode;
8
-
import java.util.List;
9
-
import org.junit.Test;
10
-
11
-
public class TestNested {
12
-
13
-
@Test
14
-
public void testNestedVars() {
15
-
String x = "1";
16
-
String y = "2";
17
-
String z = "2*x + 3*y";
18
-
String a = "2*x + 4*z";
19
-
20
-
Expression e = new Expression(a);
21
-
e.with("x", x);
22
-
e.with("y", y);
23
-
e.with("z", z);
24
-
25
-
assertEquals("34", e.eval().toString());
26
-
}
27
-
28
-
@Test
29
-
public void testReplacements() {
30
-
Expression e = new Expression("3+a+aa+aaa").with("a", "1*x")
31
-
.with("aa", "2*x").with("aaa", "3*x").with("x", "2");
32
-
assertEquals("15", e.eval().toString());
33
-
}
34
-
35
-
@Test
36
-
public void testNestedOutOfOrderVars() {
37
-
String x = "7";
38
-
String y = "5";
39
-
String z = "a+b";
40
-
String a = "p+q";
41
-
String p = "b+q";
42
-
String q = "x+y";
43
-
String b = "x*2+y";
44
-
Expression e = new Expression(z);
45
-
e.with("q", q);
46
-
e.with("p", p);
47
-
e.with("a", a);
48
-
e.with("b", b);
49
-
e.with("x", x);
50
-
e.with("y", y);
51
-
// a+b = p+q+b = b+q+q+b = 2b+2q = 2(x*2 + y) + 2(x+y) = 2(7*2+5) + 2(7+5) =
52
-
String res = e.eval().toString();
53
-
assertEquals("62", res);
54
-
}
55
-
56
-
@Test
57
-
public void testNestedOutOfOrderVarsWithFunc() {
58
-
String x = "7";
59
-
String y = "5";
60
-
String z = "a+b";
61
-
String a = "p+q";
62
-
String p = "b+q";
63
-
String q = "myAvg(x,b)";
64
-
String b = "x*2+y";
65
-
Expression e = new Expression(z);
66
-
e.with("q", q);
67
-
e.with("p", p);
68
-
e.with("a", a);
69
-
e.with("b", b);
70
-
e.with("x", x);
71
-
e.with("y", y);
72
-
e.addFunction(e.new Function("myAvg", 2) {
73
-
74
-
@Override
75
-
public BigDecimal eval(List<BigDecimal> parameters) {
76
-
assertEquals(2, parameters.size());
77
-
BigDecimal two = new BigDecimal(2);
78
-
return (parameters.get(0).add(parameters.get(1))).divide(two, MathContext.DECIMAL32);
79
-
}
80
-
});
81
-
// a+b = p+q+b = b+q+q+b = 2b+2q = 2(x*2 + y) + 2(myAvg(x+b)) = 2(7*2+5) + 2((7+(7*2+5))/2 = 38 + 26 = 64
82
-
String res = e.eval().toPlainString();
83
-
assertEquals("64", res);
84
-
}
85
-
86
-
@Test
87
-
public void testNestedOutOfOrderVarsWithOperators() {
88
-
MathContext mc = new MathContext(12, RoundingMode.HALF_UP);
89
-
Expression e = new Expression("a+y", mc);
90
-
e.addOperator(e.new Operator(">>", 30, true) {
91
-
@Override
92
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
93
-
return v1.movePointRight(v2.toBigInteger().intValue());
94
-
}
95
-
});
96
-
e.with("b", "123.45678"); // e=((123.45678 >> 2))+2+y
97
-
e.with("x", "b >> 2"); // e=((b >> 2)+2)+y
98
-
e.with("a", "X+2"); // e=(x+2)+y
99
-
e.with("y", "5"); // e=((123.45678>>2)+2)+5 = 12345.678+2+5 = 12352.678
100
-
BigDecimal res = e.eval();
101
-
String resStr = res.toPlainString();
102
-
assertEquals("12352.678", resStr);
103
-
}
104
-
}
-57
src/test/java/com/udojava/evalex/TestRPN.java
-57
src/test/java/com/udojava/evalex/TestRPN.java
···
1
-
package com.udojava.evalex;
2
-
3
-
import static org.junit.Assert.assertEquals;
4
-
5
-
import org.junit.Test;
6
-
7
-
8
-
public class TestRPN {
9
-
10
-
@Test
11
-
public void testSimple() {
12
-
13
-
assertEquals("1 2 +", new Expression("1+2").toRPN());
14
-
assertEquals("1 2 4 / +", new Expression("1+2/4").toRPN());
15
-
assertEquals("1 2 + 4 /", new Expression("(1+2)/4").toRPN());
16
-
assertEquals("1.9 2.8 + 4.7 /", new Expression("(1.9+2.8)/4.7").toRPN());
17
-
assertEquals("1.98 2.87 + 4.76 /", new Expression("(1.98+2.87)/4.76").toRPN());
18
-
assertEquals("3 4 2 * 1 5 - 2 3 ^ ^ / +",
19
-
new Expression("3 + 4 * 2 / ( 1 - 5 ) ^ 2 ^ 3").toRPN());
20
-
}
21
-
22
-
@Test
23
-
public void testFunctions() {
24
-
25
-
assertEquals("( 23.6 SIN", new Expression("SIN(23.6)").toRPN());
26
-
assertEquals("( 7 -u 8 MAX", new Expression("MAX(-7,8)").toRPN());
27
-
assertEquals("( ( 3.7 SIN ( 2.6 8.0 -u MAX MAX",
28
-
new Expression("MAX(SIN(3.7),MAX(2.6,-8.0))").toRPN());
29
-
}
30
-
31
-
@Test
32
-
public void testOperatorsInFunctions() {
33
-
34
-
assertEquals("( 23.6 4 / SIN", new Expression("SIN(23.6/4)").toRPN());
35
-
}
36
-
37
-
@Test
38
-
public void testNested() {
39
-
Expression e = new Expression("x+y");
40
-
e.with("x", "a+b");
41
-
assertEquals("a b + y +", e.toRPN());
42
-
}
43
-
44
-
@Test
45
-
public void testComplexNested() {
46
-
Expression e = new Expression("x+y");
47
-
e.with("a", "p/q");
48
-
e.with("p", "q*y");
49
-
e.with("x", "p*12"); // x y + = p 12 * y +
50
-
e.with("y", "a"); // p 12 * p q / +
51
-
e.with("p", "15");
52
-
e.with("q", "14");
53
-
String rpn = e.toRPN();
54
-
assertEquals("p 12 * p q / +", rpn);
55
-
56
-
}
57
-
}
-91
src/test/java/com/udojava/evalex/TestSciNotation.java
-91
src/test/java/com/udojava/evalex/TestSciNotation.java
···
1
-
package com.udojava.evalex;
2
-
3
-
import static org.junit.Assert.assertEquals;
4
-
5
-
import java.math.MathContext;
6
-
import org.junit.Test;
7
-
8
-
public class TestSciNotation {
9
-
10
-
@Test
11
-
public void testSimple() {
12
-
Expression e = new Expression("1e10");
13
-
assertEquals("10000000000", e.eval().toPlainString());
14
-
15
-
e = new Expression("1E10");
16
-
assertEquals("10000000000", e.eval().toPlainString());
17
-
18
-
e = new Expression("123.456E3");
19
-
assertEquals("123456", e.eval().toPlainString());
20
-
21
-
e = new Expression("2.5e0");
22
-
assertEquals("2.5", e.eval().toPlainString());
23
-
}
24
-
25
-
@Test
26
-
public void testNegative() {
27
-
Expression e = new Expression("1e-10");
28
-
assertEquals("0.0000000001", e.eval().toPlainString());
29
-
30
-
e = new Expression("1E-10");
31
-
assertEquals("0.0000000001", e.eval().toPlainString());
32
-
33
-
e = new Expression("2135E-4");
34
-
assertEquals("0.2135", e.eval().toPlainString());
35
-
}
36
-
37
-
@Test //@Ignore("Expected Failures: not implemented yet")
38
-
public void testPositive() {
39
-
Expression e = new Expression("1e+10");
40
-
assertEquals("10000000000", e.eval().toPlainString());
41
-
42
-
e = new Expression("1E+10");
43
-
assertEquals("10000000000", e.eval().toPlainString());
44
-
}
45
-
46
-
@Test
47
-
public void testCombined() {
48
-
Expression e = new Expression("sqrt(152.399025e6)", MathContext.DECIMAL64);
49
-
assertEquals("12345", e.eval().toPlainString());
50
-
51
-
e = new Expression("sin(3.e1)");
52
-
assertEquals("0.5", e.eval().toPlainString());
53
-
54
-
e = new Expression("sin( 3.e1)");
55
-
assertEquals("0.5", e.eval().toPlainString());
56
-
57
-
e = new Expression("sin(3.e1 )");
58
-
assertEquals("0.5", e.eval().toPlainString());
59
-
60
-
e = new Expression("sin( 3.e1 )");
61
-
assertEquals("0.5", e.eval().toPlainString());
62
-
63
-
e = new Expression("2.2e-16 * 10.2");
64
-
65
-
assertEquals("2.244E-15", e.eval().toString());
66
-
}
67
-
68
-
@Test(expected = NumberFormatException.class)
69
-
public void testError1() {
70
-
Expression e = new Expression("1234e-2.3");
71
-
e.eval();
72
-
}
73
-
74
-
@Test(expected = NumberFormatException.class)
75
-
public void testError2() {
76
-
Expression e = new Expression("1234e2.3");
77
-
e.eval();
78
-
}
79
-
80
-
@Test
81
-
public void testError3() {
82
-
String err = "";
83
-
Expression e = new Expression("e2");
84
-
try {
85
-
e.eval();
86
-
} catch (RuntimeException ex) {
87
-
err = ex.getMessage();
88
-
}
89
-
assertEquals("Unknown operator or function: e2", err);
90
-
}
91
-
}
-47
src/test/java/com/udojava/evalex/TestSettings.java
-47
src/test/java/com/udojava/evalex/TestSettings.java
···
1
-
package com.udojava.evalex;
2
-
3
-
import static org.junit.Assert.assertEquals;
4
-
5
-
import java.math.MathContext;
6
-
import org.junit.Test;
7
-
8
-
public class TestSettings {
9
-
10
-
@Test
11
-
public void testDefaults() {
12
-
ExpressionSettings settings = ExpressionSettings.builder().build();
13
-
assertEquals(MathContext.DECIMAL32, settings.getMathContext());
14
-
assertEquals(Expression.OPERATOR_PRECEDENCE_POWER, settings.getPowerOperatorPrecedence());
15
-
}
16
-
17
-
@Test
18
-
public void testSet() {
19
-
ExpressionSettings settings = ExpressionSettings.builder()
20
-
.mathContext(MathContext.DECIMAL128)
21
-
.powerOperatorPrecedence(99)
22
-
.build();
23
-
assertEquals(MathContext.DECIMAL128, settings.getMathContext());
24
-
assertEquals(99, settings.getPowerOperatorPrecedence());
25
-
}
26
-
27
-
@Test
28
-
public void testSetPowerHigher() {
29
-
ExpressionSettings settings = ExpressionSettings.builder()
30
-
.powerOperatorPrecedenceHigher()
31
-
.build();
32
-
assertEquals(Expression.OPERATOR_PRECEDENCE_POWER_HIGHER,
33
-
settings.getPowerOperatorPrecedence());
34
-
}
35
-
36
-
@Test
37
-
public void testDefaultPowerPrecedence() {
38
-
assertEquals("4", new Expression("-2^2").eval().toString());
39
-
}
40
-
41
-
@Test
42
-
public void testHigherPowerPrecedence() {
43
-
assertEquals("-4", new Expression("-2^2", ExpressionSettings.builder()
44
-
.powerOperatorPrecedenceHigher()
45
-
.build()).eval().toString());
46
-
}
47
-
}
-391
src/test/java/com/udojava/evalex/TestTokenizer.java
-391
src/test/java/com/udojava/evalex/TestTokenizer.java
···
1
-
package com.udojava.evalex;
2
-
3
-
import com.udojava.evalex.Expression.Token;
4
-
import com.udojava.evalex.Expression.TokenType;
5
-
import java.math.BigDecimal;
6
-
import java.util.Iterator;
7
-
8
-
import org.junit.Test;
9
-
import org.junit.function.ThrowingRunnable;
10
-
11
-
import static org.junit.Assert.assertEquals;
12
-
import static org.junit.Assert.assertFalse;
13
-
import static org.junit.Assert.assertNull;
14
-
import static org.junit.Assert.assertThrows;
15
-
import static org.junit.Assert.assertTrue;
16
-
17
-
18
-
public class TestTokenizer {
19
-
20
-
private void assertToken(String surface, Expression.TokenType type, Expression.Token actual) {
21
-
assertEquals(surface, actual.surface);
22
-
assertEquals(type, actual.type);
23
-
}
24
-
25
-
@Test
26
-
public void testSpacesFunctions() {
27
-
Expression e;
28
-
Iterator<Token> i;
29
-
30
-
e = new Expression("sin (30)");
31
-
i = e.getExpressionTokenizer();
32
-
assertToken("sin", TokenType.FUNCTION, i.next());
33
-
assertToken("(", TokenType.OPEN_PAREN, i.next());
34
-
assertToken("30", TokenType.LITERAL, i.next());
35
-
assertToken(")", TokenType.CLOSE_PAREN, i.next());
36
-
assertFalse(i.hasNext());
37
-
}
38
-
39
-
@Test
40
-
public void testSpacesFunctionsVariablesOperators() {
41
-
Expression e;
42
-
Iterator<Token> i;
43
-
44
-
e = new Expression(" sin ( 30 + x ) ");
45
-
i = e.getExpressionTokenizer();
46
-
assertToken("sin", TokenType.FUNCTION, i.next());
47
-
assertToken("(", TokenType.OPEN_PAREN, i.next());
48
-
assertToken("30", TokenType.LITERAL, i.next());
49
-
assertToken("+", TokenType.OPERATOR, i.next());
50
-
assertToken("x", TokenType.VARIABLE, i.next());
51
-
assertToken(")", TokenType.CLOSE_PAREN, i.next());
52
-
assertFalse(i.hasNext());
53
-
}
54
-
55
-
@Test
56
-
public void testNumbers() {
57
-
Expression e;
58
-
Iterator<Token> i;
59
-
60
-
e = new Expression("1");
61
-
i = e.getExpressionTokenizer();
62
-
assertToken("1", TokenType.LITERAL, i.next());
63
-
assertFalse(i.hasNext());
64
-
assertNull(i.next());
65
-
66
-
e = new Expression("-1");
67
-
i = e.getExpressionTokenizer();
68
-
assertToken("-u", TokenType.UNARY_OPERATOR, i.next());
69
-
assertToken("1", TokenType.LITERAL, i.next());
70
-
assertFalse(i.hasNext());
71
-
assertNull(i.next());
72
-
73
-
e = new Expression("123");
74
-
i = e.getExpressionTokenizer();
75
-
assertToken("123", TokenType.LITERAL, i.next());
76
-
assertFalse(i.hasNext());
77
-
assertNull(i.next());
78
-
79
-
e = new Expression("-123");
80
-
i = e.getExpressionTokenizer();
81
-
assertToken("-u", TokenType.UNARY_OPERATOR, i.next());
82
-
assertToken("123", TokenType.LITERAL, i.next());
83
-
assertFalse(i.hasNext());
84
-
assertNull(i.next());
85
-
86
-
e = new Expression("123.4");
87
-
i = e.getExpressionTokenizer();
88
-
assertToken("123.4", TokenType.LITERAL, i.next());
89
-
assertFalse(i.hasNext());
90
-
assertNull(i.next());
91
-
92
-
e = new Expression("-123.456");
93
-
i = e.getExpressionTokenizer();
94
-
assertToken("-u", TokenType.UNARY_OPERATOR, i.next());
95
-
assertToken("123.456", TokenType.LITERAL, i.next());
96
-
assertFalse(i.hasNext());
97
-
assertNull(i.next());
98
-
99
-
e = new Expression(".1");
100
-
i = e.getExpressionTokenizer();
101
-
assertToken(".1", TokenType.LITERAL, i.next());
102
-
assertFalse(i.hasNext());
103
-
assertNull(i.next());
104
-
105
-
e = new Expression("-.1");
106
-
i = e.getExpressionTokenizer();
107
-
assertToken("-u", TokenType.UNARY_OPERATOR, i.next());
108
-
assertToken(".1", TokenType.LITERAL, i.next());
109
-
assertFalse(i.hasNext());
110
-
assertNull(i.next());
111
-
}
112
-
113
-
@Test
114
-
public void testTokenizerExtraSpaces() {
115
-
Expression e = new Expression("1 ");
116
-
Iterator<Token> i = e.getExpressionTokenizer();
117
-
assertTrue(i.hasNext());
118
-
assertToken("1", TokenType.LITERAL, i.next());
119
-
assertFalse(i.hasNext());
120
-
assertNull(i.next());
121
-
122
-
e = new Expression(" ");
123
-
i = e.getExpressionTokenizer();
124
-
assertFalse(i.hasNext());
125
-
assertNull(i.next());
126
-
127
-
e = new Expression(" 1 ");
128
-
i = e.getExpressionTokenizer();
129
-
assertTrue(i.hasNext());
130
-
assertToken("1", TokenType.LITERAL, i.next());
131
-
assertFalse(i.hasNext());
132
-
assertNull(i.next());
133
-
134
-
e = new Expression(" 1 + 2 ");
135
-
i = e.getExpressionTokenizer();
136
-
assertToken("1", TokenType.LITERAL, i.next());
137
-
assertToken("+", TokenType.OPERATOR, i.next());
138
-
assertToken("2", TokenType.LITERAL, i.next());
139
-
assertFalse(i.hasNext());
140
-
assertNull(i.next());
141
-
}
142
-
143
-
@Test
144
-
public void testTokenizer1() {
145
-
Expression e = new Expression("1+2");
146
-
Iterator<Token> i = e.getExpressionTokenizer();
147
-
148
-
assertToken("1", TokenType.LITERAL, i.next());
149
-
assertToken("+", TokenType.OPERATOR, i.next());
150
-
assertToken("2", TokenType.LITERAL, i.next());
151
-
}
152
-
153
-
@Test
154
-
public void testTokenizer2() {
155
-
Expression e = new Expression("1 + 2");
156
-
Iterator<Token> i = e.getExpressionTokenizer();
157
-
158
-
assertToken("1", TokenType.LITERAL, i.next());
159
-
assertToken("+", TokenType.OPERATOR, i.next());
160
-
assertToken("2", TokenType.LITERAL, i.next());
161
-
}
162
-
163
-
@Test
164
-
public void testTokenizer3() {
165
-
Expression e = new Expression(" 1 + 2 ");
166
-
Iterator<Token> i = e.getExpressionTokenizer();
167
-
168
-
assertToken("1", TokenType.LITERAL, i.next());
169
-
assertToken("+", TokenType.OPERATOR, i.next());
170
-
assertToken("2", TokenType.LITERAL, i.next());
171
-
}
172
-
173
-
@Test
174
-
public void testTokenizer4() {
175
-
Expression e = new Expression("1+2-3/4*5");
176
-
Iterator<Token> i = e.getExpressionTokenizer();
177
-
178
-
assertToken("1", TokenType.LITERAL, i.next());
179
-
assertToken("+", TokenType.OPERATOR, i.next());
180
-
assertToken("2", TokenType.LITERAL, i.next());
181
-
assertToken("-", TokenType.OPERATOR, i.next());
182
-
assertToken("3", TokenType.LITERAL, i.next());
183
-
assertToken("/", TokenType.OPERATOR, i.next());
184
-
assertToken("4", TokenType.LITERAL, i.next());
185
-
assertToken("*", TokenType.OPERATOR, i.next());
186
-
assertToken("5", TokenType.LITERAL, i.next());
187
-
}
188
-
189
-
@Test
190
-
public void testTokenizer5() {
191
-
Expression e = new Expression("1+2.1-3.45/4.982*5.0");
192
-
Iterator<Token> i = e.getExpressionTokenizer();
193
-
194
-
assertToken("1", TokenType.LITERAL, i.next());
195
-
assertToken("+", TokenType.OPERATOR, i.next());
196
-
assertToken("2.1", TokenType.LITERAL, i.next());
197
-
assertToken("-", TokenType.OPERATOR, i.next());
198
-
assertToken("3.45", TokenType.LITERAL, i.next());
199
-
assertToken("/", TokenType.OPERATOR, i.next());
200
-
assertToken("4.982", TokenType.LITERAL, i.next());
201
-
assertToken("*", TokenType.OPERATOR, i.next());
202
-
assertToken("5.0", TokenType.LITERAL, i.next());
203
-
204
-
}
205
-
206
-
@Test
207
-
public void testTokenizer6() {
208
-
Expression e = new Expression("-3+4*-1");
209
-
Iterator<Token> i = e.getExpressionTokenizer();
210
-
211
-
assertToken("-u", TokenType.UNARY_OPERATOR, i.next());
212
-
assertToken("3", TokenType.LITERAL, i.next());
213
-
assertToken("+", TokenType.OPERATOR, i.next());
214
-
assertToken("4", TokenType.LITERAL, i.next());
215
-
assertToken("*", TokenType.OPERATOR, i.next());
216
-
assertToken("-u", TokenType.UNARY_OPERATOR, i.next());
217
-
assertToken("1", TokenType.LITERAL, i.next());
218
-
}
219
-
220
-
@Test
221
-
public void testTokenizer7() {
222
-
Expression e = new Expression("(-3+4)*-1/(7-(5*-8))");
223
-
Iterator<Token> i = e.getExpressionTokenizer();
224
-
225
-
assertToken("(", TokenType.OPEN_PAREN, i.next());
226
-
assertToken("-u", TokenType.UNARY_OPERATOR, i.next());
227
-
assertToken("3", TokenType.LITERAL, i.next());
228
-
assertToken("+", TokenType.OPERATOR, i.next());
229
-
assertToken("4", TokenType.LITERAL, i.next());
230
-
assertToken(")", TokenType.CLOSE_PAREN, i.next());
231
-
232
-
assertToken("*", TokenType.OPERATOR, i.next());
233
-
assertToken("-u", TokenType.UNARY_OPERATOR, i.next());
234
-
assertToken("1", TokenType.LITERAL, i.next());
235
-
assertToken("/", TokenType.OPERATOR, i.next());
236
-
assertToken("(", TokenType.OPEN_PAREN, i.next());
237
-
assertToken("7", TokenType.LITERAL, i.next());
238
-
assertToken("-", TokenType.OPERATOR, i.next());
239
-
assertToken("(", TokenType.OPEN_PAREN, i.next());
240
-
assertToken("5", TokenType.LITERAL, i.next());
241
-
assertToken("*", TokenType.OPERATOR, i.next());
242
-
assertToken("-u", TokenType.UNARY_OPERATOR, i.next());
243
-
assertToken("8", TokenType.LITERAL, i.next());
244
-
assertToken(")", TokenType.CLOSE_PAREN, i.next());
245
-
assertToken(")", TokenType.CLOSE_PAREN, i.next());
246
-
}
247
-
248
-
@Test
249
-
public void testTokenizer8() {
250
-
Expression e = new Expression("(1.9+2.8)/4.7");
251
-
Iterator<Token> i = e.getExpressionTokenizer();
252
-
253
-
assertToken("(", TokenType.OPEN_PAREN, i.next());
254
-
assertToken("1.9", TokenType.LITERAL, i.next());
255
-
assertToken("+", TokenType.OPERATOR, i.next());
256
-
assertToken("2.8", TokenType.LITERAL, i.next());
257
-
assertToken(")", TokenType.CLOSE_PAREN, i.next());
258
-
assertToken("/", TokenType.OPERATOR, i.next());
259
-
assertToken("4.7", TokenType.LITERAL, i.next());
260
-
261
-
}
262
-
263
-
@Test
264
-
public void testTokenizerFunction1() {
265
-
Expression e = new Expression("ABS(3.5)");
266
-
Iterator<Token> i = e.getExpressionTokenizer();
267
-
268
-
assertToken("ABS", TokenType.FUNCTION, i.next());
269
-
assertToken("(", TokenType.OPEN_PAREN, i.next());
270
-
assertToken("3.5", TokenType.LITERAL, i.next());
271
-
assertToken(")", TokenType.CLOSE_PAREN, i.next());
272
-
}
273
-
274
-
@Test
275
-
public void testTokenizerFunction2() {
276
-
Expression e = new Expression("3-ABS(3.5)/9");
277
-
Iterator<Token> i = e.getExpressionTokenizer();
278
-
279
-
assertToken("3", TokenType.LITERAL, i.next());
280
-
assertToken("-", TokenType.OPERATOR, i.next());
281
-
assertToken("ABS", TokenType.FUNCTION, i.next());
282
-
assertToken("(", TokenType.OPEN_PAREN, i.next());
283
-
assertToken("3.5", TokenType.LITERAL, i.next());
284
-
assertToken(")", TokenType.CLOSE_PAREN, i.next());
285
-
assertToken("/", TokenType.OPERATOR, i.next());
286
-
assertToken("9", TokenType.LITERAL, i.next());
287
-
288
-
}
289
-
290
-
@Test
291
-
292
-
public void testTokenizerFunction3() {
293
-
Expression e = new Expression("MAX(3.5,5.2)");
294
-
Iterator<Token> i = e.getExpressionTokenizer();
295
-
296
-
assertToken("MAX", TokenType.FUNCTION, i.next());
297
-
assertToken("(", TokenType.OPEN_PAREN, i.next());
298
-
assertToken("3.5", TokenType.LITERAL, i.next());
299
-
assertToken(",", TokenType.COMMA, i.next());
300
-
assertToken("5.2", TokenType.LITERAL, i.next());
301
-
assertToken(")", TokenType.CLOSE_PAREN, i.next());
302
-
}
303
-
304
-
@Test
305
-
public void testTokenizerFunction4() {
306
-
Expression e = new Expression("3-MAX(3.5,5.2)/9");
307
-
Iterator<Token> i = e.getExpressionTokenizer();
308
-
309
-
assertToken("3", TokenType.LITERAL, i.next());
310
-
assertToken("-", TokenType.OPERATOR, i.next());
311
-
assertToken("MAX", TokenType.FUNCTION, i.next());
312
-
assertToken("(", TokenType.OPEN_PAREN, i.next());
313
-
assertToken("3.5", TokenType.LITERAL, i.next());
314
-
assertToken(",", TokenType.COMMA, i.next());
315
-
assertToken("5.2", TokenType.LITERAL, i.next());
316
-
assertToken(")", TokenType.CLOSE_PAREN, i.next());
317
-
assertToken("/", TokenType.OPERATOR, i.next());
318
-
assertToken("9", TokenType.LITERAL, i.next());
319
-
}
320
-
321
-
@Test
322
-
public void testTokenizerFunction5() {
323
-
Expression e = new Expression("3/MAX(-3.5,-5.2)/9");
324
-
Iterator<Token> i = e.getExpressionTokenizer();
325
-
326
-
assertToken("3", TokenType.LITERAL, i.next());
327
-
assertToken("/", TokenType.OPERATOR, i.next());
328
-
assertToken("MAX", TokenType.FUNCTION, i.next());
329
-
assertToken("(", TokenType.OPEN_PAREN, i.next());
330
-
assertToken("-u", TokenType.UNARY_OPERATOR, i.next());
331
-
assertToken("3.5", TokenType.LITERAL, i.next());
332
-
assertToken(",", TokenType.COMMA, i.next());
333
-
assertToken("-u", TokenType.UNARY_OPERATOR, i.next());
334
-
assertToken("5.2", TokenType.LITERAL, i.next());
335
-
assertToken(")", TokenType.CLOSE_PAREN, i.next());
336
-
assertToken("/", TokenType.OPERATOR, i.next());
337
-
assertToken("9", TokenType.LITERAL, i.next());
338
-
}
339
-
340
-
@Test
341
-
public void testInsertImplicitMultiplication() {
342
-
Expression e = new Expression("22(3+1)");
343
-
Iterator<Token> i = e.getExpressionTokenizer();
344
-
345
-
assertToken("22", TokenType.LITERAL, i.next());
346
-
assertToken("(", TokenType.OPEN_PAREN, i.next());
347
-
assertToken("3", TokenType.LITERAL, i.next());
348
-
assertToken("+", TokenType.OPERATOR, i.next());
349
-
assertToken("1", TokenType.LITERAL, i.next());
350
-
assertToken(")", TokenType.CLOSE_PAREN, i.next());
351
-
}
352
-
353
-
@Test
354
-
public void testBracesCustomOperatorAndInIf() {
355
-
Expression e = new Expression("if( (a=0) and (b=0), 0, 1)");
356
-
e.addOperator(new AbstractOperator("AND", Expression.OPERATOR_PRECEDENCE_AND, false, true) {
357
-
@Override
358
-
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
359
-
boolean b1 = v1.compareTo(BigDecimal.ZERO) != 0;
360
-
if (!b1) {
361
-
return BigDecimal.ZERO;
362
-
}
363
-
boolean b2 = v2.compareTo(BigDecimal.ZERO) != 0;
364
-
return b2 ? BigDecimal.ONE : BigDecimal.ZERO;
365
-
}
366
-
});
367
-
368
-
BigDecimal result = e.with("a", "0").and("b", "0").eval();
369
-
assertEquals("0", result.toPlainString());
370
-
}
371
-
372
-
@Test
373
-
public void testStringLiterals() {
374
-
Iterator<Token> i = new Expression("\"foo\"").getExpressionTokenizer();
375
-
assertEquals(TokenType.STRINGPARAM, i.next().type);
376
-
assertFalse(i.hasNext());
377
-
}
378
-
379
-
@Test
380
-
public void testUnterminatedStringLiterals() {
381
-
TokenizerException ex = assertThrows(TokenizerException.class, new ThrowingRunnable() {
382
-
@Override
383
-
public void run() {
384
-
new Expression("\"foo").getExpressionTokenizer().next();
385
-
}
386
-
});
387
-
388
-
assertEquals("unterminated string literal at character position 0", ex.getMessage());
389
-
}
390
-
391
-
}
-103
src/test/java/com/udojava/evalex/TestVarArgs.java
-103
src/test/java/com/udojava/evalex/TestVarArgs.java
···
1
-
package com.udojava.evalex;
2
-
3
-
import static org.junit.Assert.assertEquals;
4
-
5
-
import com.udojava.evalex.Expression.ExpressionException;
6
-
import java.math.BigDecimal;
7
-
import java.math.MathContext;
8
-
import java.util.List;
9
-
import org.junit.Test;
10
-
11
-
public class TestVarArgs {
12
-
13
-
@Test
14
-
public void testSimple() {
15
-
Expression e = new Expression("max(1)");
16
-
assertEquals("1", e.eval().toPlainString());
17
-
18
-
e = new Expression("max(4,8)");
19
-
assertEquals("8", e.eval().toPlainString());
20
-
21
-
e = new Expression("max(12,4,8)");
22
-
assertEquals("12", e.eval().toPlainString());
23
-
24
-
e = new Expression("max(12,4,8,16,32)");
25
-
assertEquals("32", e.eval().toPlainString());
26
-
}
27
-
28
-
@Test
29
-
public void testNested() {
30
-
Expression e = new Expression("max(1,2,max(3,4,5,max(9,10,3,4,5),8),7)");
31
-
assertEquals("10", e.eval().toPlainString());
32
-
}
33
-
34
-
@Test
35
-
public void testZero() {
36
-
Expression e = new Expression("max(0)");
37
-
assertEquals("0", e.eval().toPlainString());
38
-
39
-
e = new Expression("max(0,3)");
40
-
assertEquals("3", e.eval().toPlainString());
41
-
42
-
e = new Expression("max(2,0,-3)");
43
-
assertEquals("2", e.eval().toPlainString());
44
-
45
-
e = new Expression("max(-2,0,-3)");
46
-
assertEquals("0", e.eval().toPlainString());
47
-
48
-
e = new Expression("max(0,0,0,0)");
49
-
assertEquals("0", e.eval().toPlainString());
50
-
}
51
-
52
-
@Test
53
-
public void testError() {
54
-
String err = "";
55
-
Expression e = new Expression("max()");
56
-
try {
57
-
e.eval();
58
-
} catch (ExpressionException ex) {
59
-
err = ex.getMessage();
60
-
}
61
-
assertEquals("MAX requires at least one parameter", err);
62
-
}
63
-
64
-
@Test
65
-
public void testCustomFunction1() {
66
-
Expression e = new Expression("3 * AVG(2,4)");
67
-
e.addFunction(e.new Function("AVG", -1) {
68
-
@Override
69
-
public BigDecimal eval(List<BigDecimal> parameters) {
70
-
if (parameters.size() == 0) {
71
-
throw new ExpressionException("AVG requires at least one parameter");
72
-
}
73
-
BigDecimal avg = new BigDecimal(0);
74
-
for (BigDecimal parameter : parameters) {
75
-
avg = avg.add(parameter);
76
-
}
77
-
return avg.divide(new BigDecimal(parameters.size()), MathContext.DECIMAL32);
78
-
}
79
-
});
80
-
81
-
assertEquals("9", e.eval().toPlainString());
82
-
}
83
-
84
-
@Test
85
-
public void testCustomFunction2() {
86
-
Expression e = new Expression("4 * AVG(2,4,6,8,10,12)");
87
-
e.addFunction(e.new Function("AVG", -1) {
88
-
@Override
89
-
public BigDecimal eval(List<BigDecimal> parameters) {
90
-
if (parameters.size() == 0) {
91
-
throw new ExpressionException("AVG requires at least one parameter");
92
-
}
93
-
BigDecimal avg = new BigDecimal(0);
94
-
for (BigDecimal parameter : parameters) {
95
-
avg = avg.add(parameter);
96
-
}
97
-
return avg.divide(new BigDecimal(parameters.size()), MathContext.DECIMAL32);
98
-
}
99
-
});
100
-
101
-
assertEquals("28", e.eval().toPlainString());
102
-
}
103
-
}
-65
src/test/java/com/udojava/evalex/TestVariableCharacters.java
-65
src/test/java/com/udojava/evalex/TestVariableCharacters.java
···
1
-
package com.udojava.evalex;
2
-
3
-
import static org.junit.Assert.assertEquals;
4
-
5
-
import org.junit.Test;
6
-
7
-
public class TestVariableCharacters {
8
-
9
-
@Test
10
-
public void testBadVarChar() {
11
-
String err = "";
12
-
try {
13
-
Expression expression = new Expression("a.b/2*PI+MIN(e,b)");
14
-
expression.eval();
15
-
} catch (Expression.ExpressionException e) {
16
-
err = e.getMessage();
17
-
}
18
-
assertEquals("Unknown operator . at character position 2", err);
19
-
}
20
-
21
-
@Test
22
-
public void testAddedVarChar() {
23
-
String err = "";
24
-
Expression expression;
25
-
26
-
try {
27
-
expression = new Expression("a.b/2*PI+MIN(e,b)").setVariableCharacters("_");
28
-
expression.eval();
29
-
} catch (Expression.ExpressionException e) {
30
-
err = e.getMessage();
31
-
}
32
-
assertEquals("Unknown operator . at character position 2", err);
33
-
34
-
try {
35
-
expression = new Expression("a.b/2*PI+MIN(e,b)").setVariableCharacters("_.");
36
-
expression.eval();
37
-
} catch (Expression.ExpressionException e) {
38
-
err = e.getMessage();
39
-
}
40
-
assertEquals("Unknown operator or function: a.b", err);
41
-
42
-
expression = new Expression("a.b/2*PI+MIN(e,b)").setVariableCharacters("_.");
43
-
assertEquals("5.859875", expression.with("a.b", "2").and("b", "3").eval().toPlainString());
44
-
45
-
try {
46
-
expression = new Expression(".a.b/2*PI+MIN(e,b)").setVariableCharacters("_.");
47
-
expression.eval();
48
-
} catch (Expression.ExpressionException e) {
49
-
err = e.getMessage();
50
-
}
51
-
assertEquals("Unknown unary operator . at character position 1", err);
52
-
53
-
expression = new Expression("a.b/2*PI+MIN(e,b)").setVariableCharacters("_.")
54
-
.setFirstVariableCharacters(".");
55
-
assertEquals("5.859875", expression.with("a.b", "2").and("b", "3").eval().toPlainString());
56
-
}
57
-
58
-
@Test
59
-
public void testFirstVarChar() {
60
-
Expression expression = new Expression("a.b*$PI").setVariableCharacters(".")
61
-
.setFirstVariableCharacters("$");
62
-
assertEquals("6", expression.with("a.b", "2").and("$PI", "3").eval().toPlainString());
63
-
64
-
}
65
-
}
-114
src/test/java/com/udojava/evalex/TestVariables.java
-114
src/test/java/com/udojava/evalex/TestVariables.java
···
1
-
package com.udojava.evalex;
2
-
3
-
import static org.junit.Assert.assertEquals;
4
-
import static org.junit.Assert.assertNull;
5
-
6
-
import java.math.BigDecimal;
7
-
import java.math.MathContext;
8
-
import org.junit.Test;
9
-
10
-
public class TestVariables {
11
-
12
-
@Test
13
-
public void testVars() {
14
-
assertEquals("3.141593", new Expression("PI").eval().toString());
15
-
assertEquals(
16
-
"3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679",
17
-
new Expression("PI").setPrecision(MathContext.UNLIMITED.getPrecision()).eval().toString());
18
-
assertEquals("3.141592653589793238462643383279503",
19
-
new Expression("PI").setPrecision(MathContext.DECIMAL128.getPrecision()).eval().toString());
20
-
assertEquals("3.141592653589793",
21
-
new Expression("PI").setPrecision(MathContext.DECIMAL64.getPrecision()).eval().toString());
22
-
assertEquals("3.141593",
23
-
new Expression("PI").setPrecision(MathContext.DECIMAL32.getPrecision()).eval().toString());
24
-
assertEquals("6.283186", new Expression("PI*2.0").eval().toString());
25
-
assertEquals("21",
26
-
new Expression("3*x").setVariable("x", new BigDecimal("7"))
27
-
.eval().toString());
28
-
assertEquals(
29
-
"20",
30
-
new Expression("(a^2)+(b^2)")
31
-
.setVariable("a", new BigDecimal("2"))
32
-
.setVariable("b", new BigDecimal("4")).eval()
33
-
.toPlainString());
34
-
assertEquals(
35
-
"68719480000",
36
-
new Expression("a^(2+b)^2")
37
-
.setVariable("a", "2")
38
-
.setVariable("b", "4").eval()
39
-
.toPlainString());
40
-
}
41
-
42
-
@Test
43
-
public void testSubstitution() {
44
-
Expression e = new Expression("x+y");
45
-
46
-
assertEquals("2", e.with("x", "1").and("y", "1").eval().toPlainString());
47
-
assertEquals("1", e.with("y", "0").eval().toPlainString());
48
-
assertEquals("0", e.with("x", "0").eval().toPlainString());
49
-
}
50
-
51
-
@Test
52
-
public void testWith() {
53
-
assertEquals("21",
54
-
new Expression("3*x").with("x", new BigDecimal("7"))
55
-
.eval().toString());
56
-
assertEquals(
57
-
"20",
58
-
new Expression("(a^2)+(b^2)")
59
-
.with("a", new BigDecimal("2"))
60
-
.with("b", new BigDecimal("4")).eval()
61
-
.toPlainString());
62
-
assertEquals(
63
-
"68719480000",
64
-
new Expression("a^(2+b)^2")
65
-
.with("a", "2")
66
-
.with("b", "4").eval()
67
-
.toPlainString());
68
-
69
-
assertEquals(
70
-
"68719480000",
71
-
new Expression("_a^(2+_b)^2")
72
-
.with("_a", "2")
73
-
.with("_b", "4").eval()
74
-
.toPlainString());
75
-
}
76
-
77
-
@Test
78
-
public void testNames() {
79
-
assertEquals("21",
80
-
new Expression("3*longname").with("longname", new BigDecimal("7"))
81
-
.eval().toString());
82
-
83
-
assertEquals("21",
84
-
new Expression("3*longname1").with("longname1", new BigDecimal("7"))
85
-
.eval().toString());
86
-
87
-
assertEquals("21",
88
-
new Expression("3*_longname1").with("_longname1", new BigDecimal("7"))
89
-
.eval().toString());
90
-
}
91
-
92
-
@Test(expected = Expression.ExpressionException.class)
93
-
public void failsIfVariableDoesNotExist() {
94
-
new Expression("3*unknown").eval();
95
-
}
96
-
97
-
@Test
98
-
public void testNullVariable() {
99
-
Expression e;
100
-
e = new Expression("a").with("a", "null");
101
-
assertNull(e.eval());
102
-
103
-
e = new Expression("a").with("a", (BigDecimal) null);
104
-
assertNull(e.eval());
105
-
106
-
String err = "";
107
-
try {
108
-
new Expression("a+1").with("a", "null").eval();
109
-
} catch (ArithmeticException ex) {
110
-
err = ex.getMessage();
111
-
}
112
-
assertEquals("First operand may not be null", err);
113
-
}
114
-
}