a fork of EvalEx by ezylang with a handful of breaking changes

version 3 initial commit (#297)

authored by Udo Klimaschewski and committed by GitHub b7d5e2b7 4da672c4

Changed files
+13052 -6193
.github
workflows
.travis
docs
spotless
src
main
java
com
ezylang
evalex
config
data
functions
operators
parser
udojava
java9
test
java
com
ezylang
evalex
config
data
functions
operators
parser
udojava
+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
··· 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
··· 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

This is a binary file and will not be displayed.

-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
··· 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
··· 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
··· 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
··· 1 1 EvalEx - Java Expression Evaluator 2 2 ========== 3 - [![Build Status](https://travis-ci.com/uklimaschewski/EvalEx.svg?branch=master)](https://travis-ci.com/uklimaschewski/EvalEx) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=uklimaschewski_EvalEx&metric=alert_status)](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 + ![example workflow](https://github.com/ezylang/EvalEx/actions/workflows/maven.yml/badge.svg) 5 + [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=EvalEx&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=EvalEx) 6 + [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=EvalEx&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=EvalEx) 7 + [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=EvalEx&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=EvalEx) 8 + [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=EvalEx&metric=coverage)](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>&lt;&gt;</td><td>Not equals</td></tr> 171 - <tr><td>&lt;</td><td>Less than</td></tr> 172 - <tr><td>&lt;=</td><td>Less than or equal to</td></tr> 173 - <tr><td>&gt;</td><td>Greater than</td></tr> <tr><td>&gt;=</td><td>Greater than or equal to</td></tr> 174 - <tr><td>&amp;&amp;</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
··· 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
··· 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
··· 1 + remote_theme: just-the-docs/just-the-docs 2 + title: EvalEx Documentation 3 + aux_links: 4 + "View on GitHub": 5 + - "//github.com/ezylang/EvalEx" 6 + footer_content: "Copyright &copy; 2012-2022 Udo Klimaschewski"
+6
docs/concepts/concepts.md
··· 1 + --- 2 + layout: default 3 + title: Concepts 4 + nav_order: 2 5 + has_children: true 6 + ---
+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&lt;EvaluationValue&gt;_. 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&lt;String, EvaluationValue&gt;_. 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 1 + --- 2 + layout: default 3 + title: Customization 4 + nav_order: 4 5 + has_children: true 6 + ---
+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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 + | &gt; | The greater than operator | 33 + | &gt;= | The greater equals operator | 34 + | &lt; | The less than operator | 35 + | &lt;= | The less equals operator | 36 + | && | The and operator | 37 + | &#124;&#124; | The or operator | 38 +
+6
docs/references/references.md
··· 1 + --- 2 + layout: default 3 + title: References 4 + nav_order: 9999 5 + has_children: true 6 + ---
+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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&lt;String, FunctionIfc&gt;</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
··· 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&lt;String,OperatorIfc&gt;</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
··· 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
··· 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
··· 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&lt;?&gt;</td><td>List&lt;EvaluationValue&gt; - each entry will be converted</td></tr> 83 + * <tr><td>Map&lt?,?&gt;</td><td>Map&lt;String&gt;&lt;EvaluationValue&gt; - 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
··· 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&lt;String, EvaluationValue&gt;</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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 1 - package com.udojava.evalex; 2 - 3 - public class TokenizerException extends Expression.ExpressionException { 4 - public TokenizerException(String message, int characterPosition) { 5 - super(message, characterPosition); 6 - } 7 - }
-3
src/main/java9/module-info.java
··· 1 - module com.udojava.evalex { 2 - exports com.udojava.evalex; 3 - }
+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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 - }