├── lib ├── eval_ex.dart ├── utils.dart ├── operator.dart ├── func.dart ├── lazy_if_number.dart ├── lazy_operator.dart ├── abstract_lazy_operator.dart ├── abstract_lazy_function.dart ├── abstract_unary_operator.dart ├── lazy_function.dart ├── abstract_operator.dart ├── abstract_function.dart ├── built_ins.dart └── expression.dart ├── .metadata ├── LICENSE ├── pubspec.yaml ├── test ├── factorial_test.dart ├── continuous_unary_test.dart ├── get_used_variables_test.dart ├── lazy_if_test.dart ├── rpn_test.dart ├── case_sensitive_test.dart ├── exposed_components.dart ├── sci_notation_test.dart ├── variable_characters.dart ├── var_args_test.dart ├── nested_test.dart ├── variables_test.dart ├── booleans_test.dart ├── customs_test.dart ├── tokenizer_test.dart └── eval_test.dart ├── .gitignore ├── example └── main.dart ├── CHANGELOG.md ├── README.md └── pubspec.lock /lib/eval_ex.dart: -------------------------------------------------------------------------------- 1 | library eval_ex; 2 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 216dee60c0cc9449f0b29bcf922974d612263e24 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012-2020 Udo Klimaschewski 2 | 3 | http://about.me/udo.klimaschewski 4 | http://UdoJava.com/ 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /lib/utils.dart: -------------------------------------------------------------------------------- 1 | bool isLetter(String ch) { 2 | if (ch.isEmpty) { 3 | return false; 4 | } 5 | 6 | int rune = ch.codeUnitAt(0); 7 | return (rune >= 0x41 && rune <= 0x5A) || (rune >= 0x61 && rune <= 0x7A); 8 | } 9 | 10 | bool isLetterOrDigit(String ch) { 11 | return isLetter(ch) || isDigit(ch); 12 | } 13 | 14 | // https://github.com/google/quiver-dart/blob/774b7fda30afad7537d779def2e34e47de385286/lib/strings.dart#L102 15 | bool isDigit(String? ch) { 16 | if (ch == null) { 17 | return false; 18 | } 19 | 20 | if (ch.isEmpty) { 21 | return false; 22 | } 23 | 24 | int rune = ch.codeUnitAt(0); 25 | return rune ^ 0x30 <= 9; 26 | } 27 | 28 | // https://github.com/google/quiver-dart/blob/774b7fda30afad7537d779def2e34e47de385286/lib/strings.dart#L110 29 | bool isWhitespace(String ch) { 30 | if (ch.isEmpty) { 31 | return false; 32 | } 33 | int rune = ch.codeUnitAt(0); 34 | return (rune >= 0x0009 && rune <= 0x000D) || 35 | rune == 0x0020 || 36 | rune == 0x0085 || 37 | rune == 0x00A0 || 38 | rune == 0x1680 || 39 | rune == 0x180E || 40 | (rune >= 0x2000 && rune <= 0x200A) || 41 | rune == 0x2028 || 42 | rune == 0x2029 || 43 | rune == 0x202F || 44 | rune == 0x205F || 45 | rune == 0x3000 || 46 | rune == 0xFEFF; 47 | } 48 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: eval_ex 2 | description: A mathematical and boolean expression evaluator, ported from https://github.com/uklimaschewski/EvalEx. 3 | version: 1.2.0 4 | homepage: https://github.com/RobluScouting/EvalEx 5 | 6 | environment: 7 | sdk: ">=2.12.0 <4.0.0" 8 | 9 | dependencies: 10 | decimal: ^3.1.0 11 | dart_numerics: ^0.0.6 12 | 13 | dev_dependencies: 14 | test: ^1.20.1 15 | 16 | # For information on the generic Dart part of this file, see the 17 | # following page: https://dart.dev/tools/pub/pubspec 18 | 19 | # To add assets to your package, add an assets section, like this: 20 | # assets: 21 | # - images/a_dot_burr.jpeg 22 | # - images/a_dot_ham.jpeg 23 | # 24 | # For details regarding assets in packages, see 25 | # https://flutter.dev/assets-and-images/#from-packages 26 | # 27 | # An image asset can refer to one or more resolution-specific "variants", see 28 | # https://flutter.dev/assets-and-images/#resolution-aware. 29 | 30 | # To add custom fonts to your package, add a fonts section here, 31 | # in this "flutter" section. Each entry in this list should have a 32 | # "family" key with the font family name, and a "fonts" key with a 33 | # list giving the asset and other descriptors for the font. For 34 | # example: 35 | # fonts: 36 | # - family: Schyler 37 | # fonts: 38 | # - asset: fonts/Schyler-Regular.ttf 39 | # - asset: fonts/Schyler-Italic.ttf 40 | # style: italic 41 | # - family: Trajan Pro 42 | # fonts: 43 | # - asset: fonts/TrajanPro.ttf 44 | # - asset: fonts/TrajanPro_Bold.ttf 45 | # weight: 700 46 | # 47 | # For details regarding fonts in packages, see 48 | # https://flutter.dev/custom-fonts/#from-packages 49 | -------------------------------------------------------------------------------- /lib/operator.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:decimal/decimal.dart'; 28 | import 'package:eval_ex/lazy_operator.dart'; 29 | 30 | /// Abstract class AbstractOperator accepts 31 | /// postfix unary operators with the second operand v2=null. 32 | abstract class IOperator extends ILazyOperator { 33 | /// Implementation for this operator. 34 | /// 35 | /// [v1] - Operand 1. 36 | /// [v2] - Operand 2. Null for postfix unary operators 37 | /// Returns the result of the operation. 38 | Decimal eval(Decimal? v1, Decimal? v2); 39 | } 40 | -------------------------------------------------------------------------------- /test/factorial_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:eval_ex/expression.dart'; 28 | import 'package:test/test.dart'; 29 | 30 | void main() { 31 | test("testFactorial", () { 32 | expect(Expression("FACT(0)").eval().toString(), "1"); 33 | expect(Expression("FACT(1)").eval().toString(), "1"); 34 | expect(Expression("FACT(2)").eval().toString(), "2"); 35 | expect(Expression("FACT(3)").eval().toString(), "6"); 36 | expect(Expression("FACT(4)").eval().toString(), "24"); 37 | expect(Expression("FACT(-1)").eval().toString(), "1"); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /lib/func.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:decimal/decimal.dart'; 28 | 29 | import 'lazy_function.dart'; 30 | 31 | /// Base interface which is required for all directly evaluated functions. 32 | abstract class IFunc extends ILazyFunction { 33 | /// Implementation for this function. 34 | /// 35 | /// [parameters] - Parameters will be passed by the expression evaluator as a 36 | /// [List] of BigDecimal values. 37 | /// Returns the function must return a new BigDecimal value as a 38 | /// computing result. 39 | Decimal eval(List parameters); 40 | } 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/app.flx 64 | **/ios/Flutter/app.zip 65 | **/ios/Flutter/flutter_assets/ 66 | **/ios/Flutter/flutter_export_environment.sh 67 | **/ios/ServiceDefinitions.json 68 | **/ios/Runner/GeneratedPluginRegistrant.* 69 | 70 | # Exceptions to above rules. 71 | !**/ios/**/default.mode1v3 72 | !**/ios/**/default.mode2v3 73 | !**/ios/**/default.pbxuser 74 | !**/ios/**/default.perspectivev3 75 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 76 | -------------------------------------------------------------------------------- /example/main.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:eval_ex/expression.dart'; 28 | 29 | void main() { 30 | Expression exp = Expression("2 ^ 4 + 8"); 31 | print(exp.eval().toString()); // 24 32 | 33 | // With a variable 34 | exp = Expression("a ^ b + c"); 35 | // Variables may contain alphanumeric characters, and "_". This can be changed 36 | // by using setVariableCharacters(..) and setFirstVariableCharacters(..) (what chars variable are allowed to start with). 37 | exp.setStringVariable("a", "2"); 38 | exp.setStringVariable("b", "4"); 39 | exp.setStringVariable("c", "8"); 40 | print(exp.eval().toString()); // 24 41 | 42 | // With a function 43 | exp = Expression("MAX(-7,8)"); 44 | print(exp.eval().toString()); // 8 45 | 46 | // Boolean logic 47 | exp = Expression("1>0 && 5 == 5"); 48 | print(exp.eval().toString()); // 1 49 | 50 | exp = Expression("1>0 && 5 == 4"); 51 | print(exp.eval().toString()); // 0 52 | } 53 | -------------------------------------------------------------------------------- /test/continuous_unary_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:eval_ex/expression.dart'; 28 | import 'package:test/test.dart'; 29 | 30 | void main() { 31 | test("testContinuousUnary", () { 32 | Expression expression = new Expression("++1"); 33 | expect(expression.eval()?.toBigInt().toInt(), 1); 34 | 35 | expression = new Expression("--1"); 36 | expect(expression.eval()?.toBigInt().toInt(), 1); 37 | 38 | expression = new Expression("+-1"); 39 | expect(expression.eval()?.toBigInt().toInt(), -1); 40 | 41 | expression = new Expression("-+1"); 42 | expect(expression.eval()?.toBigInt().toInt(), -1); 43 | 44 | expression = new Expression("1-+1"); 45 | expect(expression.eval()?.toBigInt().toInt(), 0); 46 | 47 | expression = new Expression("-+---+++--++-1"); 48 | expect(expression.eval()?.toBigInt().toInt(), -1); 49 | 50 | expression = new Expression("1--++++---2+-+----1"); 51 | expect(expression.eval()?.toBigInt().toInt(), -2); 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /test/get_used_variables_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:eval_ex/expression.dart'; 28 | import 'package:test/test.dart'; 29 | 30 | void main() { 31 | test("testVars", () { 32 | Expression ex = new Expression("a/2*PI+MIN(e,b)"); 33 | List usedVars = ex.getUsedVariables(); 34 | 35 | expect(usedVars.length, 2); 36 | expect(usedVars.contains("a"), true); 37 | expect(usedVars.contains("b"), true); 38 | }); 39 | 40 | test("testVarsLongNames", () { 41 | Expression ex = new Expression("var1/2*PI+MIN(var2,var3)"); 42 | List usedVars = ex.getUsedVariables(); 43 | expect(usedVars.length, 3); 44 | expect(usedVars.contains("var1"), true); 45 | expect(usedVars.contains("var2"), true); 46 | expect(usedVars.contains("var3"), true); 47 | }); 48 | 49 | test("testVarsNothing", () { 50 | Expression ex = new Expression("1/2"); 51 | List usedVars = ex.getUsedVariables(); 52 | expect(usedVars.length, 0); 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /lib/lazy_if_number.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:decimal/decimal.dart'; 28 | import 'package:eval_ex/expression.dart'; 29 | 30 | /// Lazy Number for IF function created for lazily evaluated IF condition 31 | class LazyIfNumber implements LazyNumber { 32 | List _lazyParams; 33 | 34 | LazyIfNumber(this._lazyParams); 35 | 36 | @override 37 | Decimal? eval() { 38 | LazyNumber? condition = _lazyParams[0]; 39 | 40 | if (condition == null) { 41 | throw new AssertionError("Condition may not be null."); 42 | } 43 | 44 | Decimal? result = condition.eval(); 45 | 46 | if (result == null) { 47 | throw new AssertionError("Condition may not be null."); 48 | } 49 | 50 | bool isTrue = result.compareTo(Decimal.zero) != 0; 51 | return isTrue ? _lazyParams[1]?.eval() : _lazyParams[2]?.eval(); 52 | } 53 | 54 | @override 55 | String getString() { 56 | LazyNumber? condition = _lazyParams[0]; 57 | if (condition == null) { 58 | throw new AssertionError("Condition may not be null."); 59 | } 60 | 61 | return condition.getString(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.1.8] - 7/31/2023 2 | Bumping decimal version to 2.3.3 and allowing for case sensitive functions, operators, and variables. 3 | 4 | ## [1.1.7] - 9/20/2022 5 | Bumping decimal version to 2.3.0 (fixes power function builtin error) 6 | 7 | ## [1.1.6] - 2/19/2022 8 | More static analysis issues fixed. 9 | 10 | ## [1.1.5] - 2/19/2022 11 | Fixed some static analysis issues, switching to Dart unit test library. 12 | 13 | ## [1.1.4] - 2/19/2022 14 | Completely removed Flutter dependency except for dev unit tests. 15 | 16 | ## [1.1.3] - 2/19/2022 17 | Removed dependency on Flutter, fixed file formatting. 18 | 19 | ## [1.1.2] - 2/16/2022 20 | Switched to Decimal 2.1.0 21 | 22 | ## [1.1.1] - 2/11/2022 23 | Bumped decimal dependency to latest version 24 | 25 | ## [1.1.0] - 7/5/2021 26 | Switched to null safety and updated dependencies. Added support for suffix/postfix operators 27 | from commit: https://github.com/uklimaschewski/EvalEx/commit/ac55e57426f698bbe8fd966294796d83a4f27d31 28 | as well as a bug fix for string variables from commit: 29 | https://github.com/uklimaschewski/EvalEx/commit/cb66e7065325e1117371e6aad1cd7955088dd33d. 30 | 31 | ## [1.0.12] - 12/29/2020 32 | Fixing static analysis issues introduced in latest Flutter releases 33 | 34 | ## [1.0.11] - 12/29/2020 35 | Fixing static analysis issues introduced in latest Flutter releases 36 | 37 | ## [1.0.10] - 11/16/2020 38 | Fixed formatting 39 | 40 | ## [1.0.9] - 11/16/2020 41 | Bug fixes to ">" and "<" operators. 42 | 43 | ## [1.0.8] - 9/15/2020 44 | Added STREQ function 45 | 46 | ## [1.0.7] - 9/15/2020 47 | Made error text more consistent for "^" 48 | 49 | ## [1.0.6] - 9/15/2020 50 | Typo spelling fix 51 | 52 | ## [1.0.5] - 9/15/2020 53 | Typo spelling fix 54 | 55 | ## [1.0.4] - 9/15/2020 56 | Switch assert errors for safeguards for "^" and "FACT" to ExpressionExceptions 57 | 58 | ## [1.0.3] - 9/15/2020 59 | Added safeguards for "^" and "FACT" on really large numbers so they don't 60 | kill evaluation performance. 61 | 62 | ## [1.0.2] - 9/15/2020 63 | Fixed formatting 64 | 65 | ## [1.0.1] - 9/15/2020 66 | Added examples and fixed formatting. 67 | 68 | ## [1.0.0] - 9/15/2020 69 | Initial port of https://github.com/uklimaschewski/EvalEx. 70 | Differences in implementation currently: 71 | - Hyperbolic functions aren't supported 72 | - The SQRT function is currently limited to 64-bit precision 73 | - Built in operators/functions/variables are instead defined in built_ins.dart 74 | - Precision is handled slightly differently (no math context) 75 | -------------------------------------------------------------------------------- /lib/lazy_operator.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:eval_ex/expression.dart'; 28 | 29 | /// Base interface which is required for all operators. 30 | abstract class ILazyOperator { 31 | /// Gets the String that is used to denote the operator in the expression. 32 | /// 33 | /// Returns the String that is used to denote the operator in the expression. 34 | String getOper(); 35 | 36 | /// Gets the precedence value of this operator. 37 | /// 38 | /// Returns the precedence value of this operator. 39 | int getPrecedence(); 40 | 41 | /// Gets whether this operator is left associative (true) or if 42 | /// this operator is right associative (false). 43 | /// 44 | /// Returns `true` if this operator is left associative. 45 | bool isLeftAssoc(); 46 | 47 | /// Gets whether this operator evaluates to a boolean expression. 48 | /// Returns `true` if this operator evaluates to a boolean expression. 49 | bool isBooleanOperator(); 50 | 51 | /// True if the number of operands for this operator is 1, false if 52 | /// it is 2. 53 | bool isUnaryOperator(); 54 | 55 | /// Implementation for this operator. 56 | /// 57 | /// [v1] - Operand 1. 58 | /// [v2] - Operand 2. 59 | /// Returns the result of the operation. 60 | LazyNumber evalLazy(LazyNumber v1, LazyNumber? v2); 61 | } 62 | -------------------------------------------------------------------------------- /lib/abstract_lazy_operator.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'lazy_operator.dart'; 28 | 29 | /// Abstract implementation of an operator. 30 | abstract class AbstractLazyOperator implements ILazyOperator { 31 | /// This operators name (pattern). 32 | String oper; 33 | 34 | /// Operators precedence. 35 | int precedence; 36 | 37 | /// Operator is left associative. 38 | bool leftAssoc; 39 | 40 | /// Whether this operator is boolean or not. 41 | bool booleanOperator; 42 | 43 | bool unaryOperator; 44 | 45 | /// Creates a new operator. 46 | /// 47 | /// [oper] - The operator name (pattern). 48 | /// [precedence] - The operators precedence. 49 | /// [leftAssoc] - `true` if the operator is left associative, else `false`. 50 | /// [booleanOperator] - Whether this operator is boolean. 51 | AbstractLazyOperator(this.oper, this.precedence, this.leftAssoc, 52 | {this.booleanOperator = false, this.unaryOperator = false}); 53 | 54 | @override 55 | String getOper() { 56 | return oper; 57 | } 58 | 59 | @override 60 | int getPrecedence() { 61 | return precedence; 62 | } 63 | 64 | @override 65 | bool isLeftAssoc() { 66 | return leftAssoc; 67 | } 68 | 69 | @override 70 | bool isBooleanOperator() { 71 | return booleanOperator; 72 | } 73 | 74 | @override 75 | bool isUnaryOperator() { 76 | return unaryOperator; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/abstract_lazy_function.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'lazy_function.dart'; 28 | 29 | /// Abstract implementation of a lazy function which implements all necessary 30 | /// methods with the exception of the main logic. 31 | abstract class AbstractLazyFunction implements ILazyFunction { 32 | /// Name of this function. 33 | String name; 34 | 35 | /// Number of parameters expected for this function. -1 36 | /// denotes a variable number of parameters. 37 | int numParams; 38 | 39 | /// Whether this function is a boolean function. 40 | bool booleanFunction; 41 | 42 | /// Creates a new function with given name and parameter count. 43 | /// 44 | /// [name] - The name of the function. 45 | /// [numParams] - The number of parameters for this function. 46 | /// `-1<` denotes a variable number of parameters. 47 | /// [booleanFunction] Whether this function is a boolean function. 48 | AbstractLazyFunction(String name, this.numParams, 49 | {this.booleanFunction = false}) 50 | : name = name.toUpperCase(); 51 | 52 | @override 53 | String getName() { 54 | return name; 55 | } 56 | 57 | @override 58 | int getNumParams() { 59 | return numParams; 60 | } 61 | 62 | @override 63 | bool numParamsVaries() { 64 | return numParams < 0; 65 | } 66 | 67 | @override 68 | bool isBooleanFunction() { 69 | return booleanFunction; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/lazy_if_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:decimal/decimal.dart'; 28 | import 'package:eval_ex/expression.dart'; 29 | import 'package:test/test.dart'; 30 | 31 | void main() { 32 | test("testLazyIf", () { 33 | Expression expression = new Expression("if(a=0,0,12/a)") 34 | ..setDecimalVariable("a", Decimal.zero); 35 | expect(expression.eval(), Decimal.zero); 36 | }); 37 | 38 | test("testLazyIfWithNestedFunctions", () { 39 | Expression expression = new Expression("if(a=0,0,abs(12/a))") 40 | ..setDecimalVariable("a", Decimal.zero); 41 | expect(expression.eval(), Decimal.zero); 42 | }); 43 | 44 | test("testLazyIfWithNestedSuccessIf", () { 45 | Expression expression = new Expression("if(a=0,0,if(5/a>3,2,4))") 46 | ..setDecimalVariable("a", Decimal.zero); 47 | expect(expression.eval(), Decimal.zero); 48 | }); 49 | 50 | test("testLazyIfWithNestedFailingIf()", () { 51 | Expression expression = new Expression("if(a=0,if(5/a>3,2,4),0)") 52 | ..setDecimalVariable("a", Decimal.zero); 53 | 54 | expect(() => expression.eval(), throwsA(isA())); 55 | }); 56 | 57 | test("testLazyIfWithNull", () { 58 | String err = ""; 59 | 60 | try { 61 | new Expression("if(a,0,12/a)")..setStringVariable("a", "null").eval(); 62 | } on AssertionError catch (e) { 63 | err = e.message.toString(); 64 | } 65 | 66 | expect(err, "Condition may not be null."); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /lib/abstract_unary_operator.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:decimal/decimal.dart'; 28 | import 'package:eval_ex/abstract_operator.dart'; 29 | 30 | import 'expression.dart'; 31 | 32 | /// Abstract implementation of an unary operator.
33 | /// This abstract implementation implements eval so that it forwards its first 34 | /// parameter to evalUnary. 35 | abstract class AbstractUnaryOperator extends AbstractOperator { 36 | /// Creates a new operator. 37 | /// 38 | /// [oper] - The operator name (pattern). 39 | /// [precedence] - The operators precedence. 40 | /// [leftAssoc] - `true` if the operator is left associative, else `false<` 41 | AbstractUnaryOperator(String oper, int precedence, bool leftAssoc) 42 | : super(oper, precedence, leftAssoc); 43 | 44 | @override 45 | LazyNumber evalLazy(final LazyNumber v1, final LazyNumber? v2) { 46 | if (v2 != null) { 47 | throw ExpressionException( 48 | "Did not expect a second parameter for unary operator"); 49 | } 50 | 51 | return LazyNumberImpl(eval: () { 52 | return evalUnary(v1.eval()); 53 | }, getString: () { 54 | return evalUnary(v1.eval()).toString(); 55 | }); 56 | } 57 | 58 | @override 59 | Decimal eval(Decimal? v1, Decimal? v2) { 60 | if (v2 != null) { 61 | throw ExpressionException( 62 | "Did not expect a second parameter for unary operator"); 63 | } 64 | 65 | return evalUnary(v1); 66 | } 67 | 68 | Decimal evalUnary(Decimal? v1); 69 | } 70 | -------------------------------------------------------------------------------- /lib/lazy_function.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:eval_ex/expression.dart'; 28 | 29 | /// Base interface which is required for lazily evaluated functions. A function 30 | /// is defined by a name, a number of parameters it accepts and of course 31 | /// the logic for evaluating the result. 32 | abstract class ILazyFunction { 33 | /// Gets the name of this function.
34 | ///
35 | /// The name is use to invoke this function in the expression. 36 | /// 37 | /// Returns the name of this function. 38 | String getName(); 39 | 40 | /// Gets the number of parameters this function accepts.
41 | /// 42 | /// A value of -1 denotes that this function accepts a variable 43 | /// number of parameters. 44 | /// 45 | /// Returns the number of parameters this function accepts. 46 | int getNumParams(); 47 | 48 | /// Gets whether the number of accepted parameters varies.
49 | /// 50 | /// That means that the function does accept an undefined amount of 51 | /// parameters. 52 | /// 53 | /// Returns `true` if the number of accepted parameters varies. 54 | bool numParamsVaries(); 55 | 56 | /// Gets whether this function evaluates to a boolean expression. 57 | /// 58 | /// Returns `true`> if this function evaluates to a boolean expression. 59 | bool isBooleanFunction(); 60 | 61 | /// Lazily evaluate this function. 62 | /// 63 | /// [lazyParams] - The accepted parameters. 64 | /// Returns the lazy result of this function. 65 | LazyNumber lazyEval(List lazyParams); 66 | } 67 | -------------------------------------------------------------------------------- /lib/abstract_operator.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:decimal/decimal.dart'; 28 | import 'package:eval_ex/abstract_lazy_operator.dart'; 29 | import 'package:eval_ex/expression.dart'; 30 | import 'package:eval_ex/operator.dart'; 31 | 32 | /// Abstract implementation of an operator. 33 | abstract class AbstractOperator extends AbstractLazyOperator 34 | implements IOperator { 35 | /// Creates a new operator. 36 | /// 37 | /// [oper] - The operator name (pattern). 38 | /// [precedence] - The operators precedence. 39 | /// [leftAssoc] - `true` if the operator is left associative, else `false<` 40 | /// [booleanOperator] Whether this operator is boolean. 41 | /// [unaryOperator] Whether the operator takes one or two operands 42 | AbstractOperator(String oper, int precedence, bool leftAssoc, 43 | {bool booleanOperator = false, bool unaryOperator = false}) 44 | : super(oper, precedence, leftAssoc, 45 | booleanOperator: booleanOperator, unaryOperator: unaryOperator); 46 | 47 | LazyNumber evalLazy(final LazyNumber v1, final LazyNumber? v2) { 48 | return _LazyNumberImpl(this, v1, v2); 49 | } 50 | } 51 | 52 | class _LazyNumberImpl extends LazyNumber { 53 | AbstractOperator _abstractOperator; 54 | LazyNumber _v1; 55 | LazyNumber? _v2; 56 | 57 | _LazyNumberImpl(this._abstractOperator, this._v1, this._v2); 58 | 59 | @override 60 | Decimal eval() { 61 | return _abstractOperator.eval(_v1.eval(), _v2?.eval()); 62 | } 63 | 64 | @override 65 | String getString() { 66 | return eval().toString(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/rpn_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:eval_ex/expression.dart'; 28 | import 'package:test/test.dart'; 29 | 30 | void main() { 31 | test("testSimple", () { 32 | expect(Expression("1+2").toRPN(), "1 2 +"); 33 | expect(Expression("1+2/4").toRPN(), "1 2 4 / +"); 34 | expect(Expression("(1+2)/4").toRPN(), "1 2 + 4 /"); 35 | expect(Expression("(1.9+2.8)/4.7").toRPN(), "1.9 2.8 + 4.7 /"); 36 | expect(Expression("(1.98+2.87)/4.76").toRPN(), "1.98 2.87 + 4.76 /"); 37 | expect(Expression("3 + 4 * 2 / ( 1 - 5 ) ^ 2 ^ 3").toRPN(), 38 | "3 4 2 * 1 5 - 2 3 ^ ^ / +"); 39 | }); 40 | 41 | test("testFunctions", () { 42 | expect(Expression("SIN(23.6)").toRPN(), "( 23.6 SIN"); 43 | expect(Expression("MAX(-7,8)").toRPN(), "( 7 -u 8 MAX"); 44 | expect(Expression("MAX(SIN(3.7),MAX(2.6,-8.0))").toRPN(), 45 | "( ( 3.7 SIN ( 2.6 8.0 -u MAX MAX"); 46 | }); 47 | 48 | test("testOperatorsInFunctions", () { 49 | expect(Expression("SIN(23.6/4)").toRPN(), "( 23.6 4 / SIN"); 50 | }); 51 | 52 | test("testNested", () { 53 | Expression e = new Expression("x+y"); 54 | e.setStringVariable("x", "a+b"); 55 | expect(e.toRPN(), "a b + y +"); 56 | }); 57 | 58 | test("testComplexNested", () { 59 | Expression e = new Expression("x+y"); 60 | e.setStringVariable("a", "p/q"); 61 | e.setStringVariable("p", "q*y"); 62 | e.setStringVariable("x", "p*12"); // x y + = p 12 * y + 63 | e.setStringVariable("y", "a"); // p 12 * p q / + 64 | e.setStringVariable("p", "15"); 65 | e.setStringVariable("q", "14"); 66 | String rpn = e.toRPN(); 67 | expect(rpn, "p 12 * p q / +"); 68 | }); 69 | } 70 | -------------------------------------------------------------------------------- /lib/abstract_function.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:decimal/decimal.dart'; 28 | 29 | import 'abstract_lazy_function.dart'; 30 | import 'expression.dart'; 31 | import 'func.dart'; 32 | 33 | /// Abstract implementation of a direct function.
34 | /// 35 | /// This abstract implementation does implement lazyEval so that it returns 36 | /// the result of eval. 37 | abstract class AbstractFunction extends AbstractLazyFunction implements IFunc { 38 | /// Creates a new function with given name and parameter count. 39 | /// 40 | /// [name] - The name of the function. 41 | /// [numParams] - The number of parameters for this function. 42 | /// `-1` denotes a variable number of parameters. 43 | /// [booleanFunction] Whether this function is a boolean function. 44 | AbstractFunction(String name, int numParams, {bool booleanFunction = false}) 45 | : super(name, numParams, booleanFunction: booleanFunction); 46 | 47 | LazyNumber lazyEval(final List lazyParams) { 48 | return _LazyNumberImpl(this, lazyParams); 49 | } 50 | } 51 | 52 | class _LazyNumberImpl extends LazyNumber { 53 | AbstractFunction _abstractFunction; 54 | List _lazyParams; 55 | List? _params; 56 | 57 | _LazyNumberImpl(this._abstractFunction, this._lazyParams); 58 | 59 | @override 60 | Decimal eval() { 61 | return _abstractFunction.eval(_getParams()); 62 | } 63 | 64 | @override 65 | String getString() { 66 | return eval().toString(); 67 | } 68 | 69 | List _getParams() { 70 | if (_params == null) { 71 | _params = _lazyParams.map((e) => e.eval()).toList(); 72 | } 73 | 74 | return _params!; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /test/case_sensitive_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:decimal/decimal.dart'; 28 | import 'package:eval_ex/built_ins.dart'; 29 | import 'package:eval_ex/expression.dart'; 30 | import 'package:test/test.dart'; 31 | 32 | void main() { 33 | test("testVariableIsCaseSensitive", () { 34 | Expression expression = new Expression("a"); 35 | expression.setDecimalVariable("A", Decimal.fromInt(20)); 36 | expect(() => expression.eval()?.toBigInt().toInt(), 37 | throwsA(TypeMatcher())); 38 | 39 | expression = new Expression("a + B"); 40 | expression.setDecimalVariable("A", Decimal.fromInt(10)); 41 | expression.setDecimalVariable("b", Decimal.fromInt(10)); 42 | expect(() => expression.eval()?.toBigInt().toInt(), 43 | throwsA(TypeMatcher())); 44 | 45 | expression = new Expression("a+B"); 46 | expression.setStringVariable("A", "c+d"); 47 | expression.setDecimalVariable("b", Decimal.fromInt(10)); 48 | expression.setDecimalVariable("C", Decimal.fromInt(5)); 49 | expression.setDecimalVariable("d", Decimal.fromInt(5)); 50 | expect(() => expression.eval()?.toBigInt().toInt(), 51 | throwsA(TypeMatcher())); 52 | }); 53 | 54 | test("testFunctionCaseSensitive", () { 55 | Expression expression = Expression("a+testsum(1,3)"); 56 | expression.setDecimalVariable("A", Decimal.one); 57 | expression.addFunc(FunctionImpl("testSum", -1, fEval: (params) { 58 | Decimal? value; 59 | for (Decimal d in params) { 60 | value = value == null ? d : value + d; 61 | } 62 | return value; 63 | })); 64 | 65 | expect( 66 | () => expression.eval(), throwsA(TypeMatcher())); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /test/exposed_components.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:decimal/decimal.dart'; 28 | import 'package:eval_ex/built_ins.dart'; 29 | import 'package:eval_ex/expression.dart'; 30 | import 'package:test/test.dart'; 31 | 32 | void main() { 33 | test("testDeclaredOperators", () { 34 | Expression expression = new Expression("c+d"); 35 | int originalOperator = expression.getDeclaredOperators().length; 36 | 37 | expression.addOperator(OperatorImpl("\$\$", -1, true, fEval: (v1, v2) { 38 | return null; 39 | })); 40 | 41 | // "Operator List should have the new $$ operator" 42 | expect(expression.getDeclaredOperators().contains("\$\$"), true); 43 | // "Should have an extra operators" 44 | expect(expression.getDeclaredOperators().length, originalOperator + 1); 45 | }); 46 | 47 | test("testDeclaredVariables", () { 48 | Expression expression = new Expression("c+d"); 49 | int originalCounts = expression.getDeclaredVariables().length; 50 | expression.setDecimalVariable("var1", Decimal.fromInt(12)); 51 | 52 | // "Function list should have new func1 function declared" 53 | expect(expression.getDeclaredVariables().contains("var1"), true); 54 | // "Function list should have one more function declared" 55 | expect(expression.getDeclaredVariables().length, originalCounts + 1); 56 | }); 57 | 58 | test("testDeclaredFunctionGetter", () { 59 | Expression expression = new Expression("c+d"); 60 | int originalFunctionCount = expression.getDeclaredFunctions().length; 61 | 62 | expression.addFunc(FunctionImpl("func1", 3, fEval: (params) { 63 | return null; 64 | })); 65 | 66 | // "Function list should have new func1 function declared" 67 | expect(expression.getDeclaredFunctions().contains("FUNC1"), true); 68 | // "Function list should have one more function declared" 69 | expect(expression.getDeclaredFunctions().length, originalFunctionCount + 1); 70 | }); 71 | } 72 | -------------------------------------------------------------------------------- /test/sci_notation_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:eval_ex/expression.dart'; 28 | import 'package:test/test.dart'; 29 | 30 | void main() { 31 | test("testSimple", () { 32 | Expression e = new Expression("1e10"); 33 | expect(e.eval().toString(), "10000000000"); 34 | 35 | e = new Expression("1E10"); 36 | expect(e.eval().toString(), "10000000000"); 37 | 38 | e = new Expression("123.456E3"); 39 | expect(e.eval().toString(), "123456"); 40 | 41 | e = new Expression("2.5e0"); 42 | expect(e.eval().toString(), "2.5"); 43 | }); 44 | 45 | test("testNegative", () { 46 | Expression e = new Expression("1e-10"); 47 | expect(e.eval().toString(), "0.0000000001"); 48 | 49 | e = new Expression("1E-10"); 50 | expect(e.eval().toString(), "0.0000000001"); 51 | 52 | e = new Expression("2135E-4"); 53 | expect(e.eval().toString(), "0.2135"); 54 | }); 55 | 56 | test("testPositive", () { 57 | Expression e = new Expression("1e+10"); 58 | expect(e.eval().toString(), "10000000000"); 59 | 60 | e = new Expression("1E+10"); 61 | expect(e.eval().toString(), "10000000000"); 62 | }); 63 | 64 | test("testCombined", () { 65 | Expression e = new Expression("sqrt(152.399025e6)"); 66 | expect(e.eval().toString(), "12345"); 67 | 68 | e = new Expression("sin(3.e1)"); 69 | expect(e.eval().toString(), "0.49999999999999994"); 70 | 71 | e = new Expression("sin( 3.e1)"); 72 | expect(e.eval().toString(), "0.49999999999999994"); 73 | 74 | e = new Expression("sin(3.e1 )"); 75 | expect(e.eval().toString(), "0.49999999999999994"); 76 | 77 | e = new Expression("sin( 3.e1 )"); 78 | expect(e.eval().toString(), "0.49999999999999994"); 79 | 80 | e = new Expression("2.2e-16 * 10.2"); 81 | expect(e.eval().toString(), "0.000000000000002244"); 82 | }); 83 | 84 | test("testError1", () { 85 | Expression e = new Expression("1234e-2.3"); 86 | expect(() => e.eval(), throwsA(isA())); 87 | }); 88 | 89 | test("testError2", () { 90 | Expression e = new Expression("1234e2.3"); 91 | expect(() => e.eval(), throwsA(isA())); 92 | }); 93 | 94 | test("testError3", () { 95 | String err = ""; 96 | Expression e = new Expression("e2"); 97 | try { 98 | e.eval(); 99 | } on ExpressionException catch (e) { 100 | err = e.msg; 101 | } 102 | 103 | expect(err, "Unknown operator or function: e2"); 104 | }); 105 | } 106 | -------------------------------------------------------------------------------- /test/variable_characters.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:eval_ex/expression.dart'; 28 | import 'package:test/test.dart'; 29 | 30 | void main() { 31 | test("testBadVarChar", () { 32 | String err = ""; 33 | try { 34 | Expression expression = new Expression("a.b/2*PI+MIN(e,b)"); 35 | expression.eval(); 36 | } on ExpressionException catch (e) { 37 | err = e.msg; 38 | } 39 | expect(err, "Unknown operator . at character position 2"); 40 | }); 41 | 42 | test("testAddedVarChar", () { 43 | String err = ""; 44 | Expression expression; 45 | 46 | try { 47 | expression = 48 | new Expression("a.b/2*PI+MIN(e,b)").setVariableCharacters("_"); 49 | expression.eval(); 50 | } on ExpressionException catch (e) { 51 | err = e.msg; 52 | } 53 | expect(err, "Unknown operator . at character position 2"); 54 | 55 | try { 56 | expression = 57 | new Expression("a.b/2*PI+MIN(e,b)").setVariableCharacters("_."); 58 | expression.eval(); 59 | } on ExpressionException catch (e) { 60 | err = e.msg; 61 | } 62 | expect(err, "Unknown operator or function: a.b"); 63 | 64 | expression = 65 | new Expression("a.b/2*PI+MIN(e,b)").setVariableCharacters("_."); 66 | expect( 67 | (expression 68 | ..setStringVariable("a.b", "2") 69 | ..setStringVariable("b", "3")) 70 | .eval() 71 | ?.toStringAsPrecision(7), 72 | "5.859874"); 73 | 74 | try { 75 | expression = 76 | new Expression(".a.b/2*PI+MIN(e,b)").setVariableCharacters("_."); 77 | expression.eval(); 78 | } on ExpressionException catch (e) { 79 | err = e.msg; 80 | } 81 | 82 | expect(err, "Unknown unary operator . at character position 1"); 83 | 84 | expression = new Expression("a.b/2*PI+MIN(e,b)") 85 | .setVariableCharacters("_.") 86 | .setFirstVariableCharacters("."); 87 | expect( 88 | (expression 89 | ..setStringVariable("a.b", "2") 90 | ..setStringVariable("b", "3")) 91 | .eval() 92 | ?.toStringAsFixed(7), 93 | "5.8598745"); 94 | }); 95 | 96 | test("testFirstVarChar", () { 97 | Expression expression = new Expression("a.b*\$PI") 98 | .setVariableCharacters(".") 99 | .setFirstVariableCharacters("\$"); 100 | expect( 101 | (expression 102 | ..setStringVariable("a.b", "2") 103 | ..setStringVariable("\$PI", "3")) 104 | .eval() 105 | .toString(), 106 | "6"); 107 | }); 108 | } 109 | -------------------------------------------------------------------------------- /test/var_args_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:decimal/decimal.dart'; 28 | import 'package:eval_ex/built_ins.dart'; 29 | import 'package:eval_ex/expression.dart'; 30 | import 'package:test/test.dart'; 31 | 32 | void main() { 33 | test("testSimple", () { 34 | Expression e = new Expression("max(1)"); 35 | expect(e.eval().toString(), "1"); 36 | 37 | e = new Expression("max(4,8)"); 38 | expect(e.eval().toString(), "8"); 39 | 40 | e = new Expression("max(12,4,8)"); 41 | expect(e.eval().toString(), "12"); 42 | 43 | e = new Expression("max(12,4,8,16,32)"); 44 | expect(e.eval().toString(), "32"); 45 | }); 46 | 47 | test("testNested", () { 48 | Expression e = new Expression("max(1,2,max(3,4,5,max(9,10,3,4,5),8),7)"); 49 | expect(e.eval().toString(), "10"); 50 | }); 51 | 52 | test("testZero", () { 53 | Expression e = new Expression("max(0)"); 54 | expect(e.eval().toString(), "0"); 55 | 56 | e = new Expression("max(0,3)"); 57 | expect(e.eval().toString(), "3"); 58 | 59 | e = new Expression("max(2,0,-3)"); 60 | expect(e.eval().toString(), "2"); 61 | 62 | e = new Expression("max(-2,0,-3)"); 63 | expect(e.eval().toString(), "0"); 64 | 65 | e = new Expression("max(0,0,0,0)"); 66 | expect(e.eval().toString(), "0"); 67 | }); 68 | 69 | test("testError", () { 70 | String err = ""; 71 | Expression e = new Expression("max()"); 72 | 73 | try { 74 | e.eval(); 75 | } on ExpressionException catch (e) { 76 | err = e.msg; 77 | } 78 | 79 | expect(err, "MAX requires at least one parameter"); 80 | }); 81 | 82 | test("testCustomFunction1", () { 83 | Expression e = new Expression("3 * AVG(2,4)"); 84 | e.addFunc(FunctionImpl("AVG", -1, fEval: (params) { 85 | if (params.length == 0) { 86 | throw new ExpressionException("AVG requires at least one parameter"); 87 | } 88 | 89 | Decimal avg = Decimal.zero; 90 | for (Decimal parameter in params) { 91 | avg = avg + parameter; 92 | } 93 | return (avg / Decimal.fromInt(params.length)).toDecimal(); 94 | })); 95 | 96 | expect(e.eval().toString(), "9"); 97 | }); 98 | 99 | test("testCustomFunction2", () { 100 | Expression e = new Expression("4 * AVG(2,4,6,8,10,12)"); 101 | e.addFunc(FunctionImpl("AVG", -1, fEval: (params) { 102 | if (params.length == 0) { 103 | throw new ExpressionException("AVG requires at least one parameter"); 104 | } 105 | 106 | Decimal avg = Decimal.zero; 107 | for (Decimal parameter in params) { 108 | avg = avg + parameter; 109 | } 110 | return (avg / Decimal.fromInt(params.length)).toDecimal(); 111 | })); 112 | 113 | expect(e.eval().toString(), "28"); 114 | }); 115 | } 116 | -------------------------------------------------------------------------------- /test/nested_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:decimal/decimal.dart'; 28 | import 'package:eval_ex/built_ins.dart'; 29 | import 'package:eval_ex/expression.dart'; 30 | import 'package:test/test.dart'; 31 | 32 | void main() { 33 | test("testNestedVars", () { 34 | String x = "1"; 35 | String y = "2"; 36 | String z = "2*x + 3*y"; 37 | String a = "2*x + 4*z"; 38 | 39 | Expression e = new Expression(a) 40 | ..setStringVariable("x", x) 41 | ..setStringVariable("y", y) 42 | ..setStringVariable("z", z); 43 | 44 | expect(e.eval().toString(), "34"); 45 | }); 46 | 47 | test("testReplacements", () { 48 | Expression e = new Expression("3+a+aa+aaa") 49 | ..setStringVariable("a", "1*x") 50 | ..setStringVariable("aa", "2*x") 51 | ..setStringVariable("aaa", "3*x") 52 | ..setStringVariable("x", "2"); 53 | 54 | expect(e.eval().toString(), "15"); 55 | }); 56 | 57 | test("testNestedOutOfOrdersVars", () { 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 = "x+y"; 64 | String b = "x*2+y"; 65 | Expression e = new Expression(z) 66 | ..setStringVariable("q", q) 67 | ..setStringVariable("p", p) 68 | ..setStringVariable("a", a) 69 | ..setStringVariable("b", b) 70 | ..setStringVariable("x", x) 71 | ..setStringVariable("y", y); 72 | 73 | // 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) = 74 | String res = e.eval().toString(); 75 | expect(res, "62"); 76 | }); 77 | 78 | test("testNestedOutOfOrderVarsWithFunc", () { 79 | String x = "7"; 80 | String y = "5"; 81 | String z = "a+b"; 82 | String a = "p+q"; 83 | String p = "b+q"; 84 | String q = "myAvg(x,b)"; 85 | String b = "x*2+y"; 86 | Expression e = new Expression(z) 87 | ..setStringVariable("q", q) 88 | ..setStringVariable("p", p) 89 | ..setStringVariable("a", a) 90 | ..setStringVariable("b", b) 91 | ..setStringVariable("x", x) 92 | ..setStringVariable("y", y); 93 | 94 | e.addFunc(FunctionImpl("myAvg", 2, fEval: (params) { 95 | expect(params.length, 2); 96 | Decimal two = Decimal.fromInt(2); 97 | Decimal res = ((params[0] + params[1]) / two).toDecimal(); 98 | return res; 99 | })); 100 | 101 | // 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 102 | String res = e.eval().toString(); 103 | expect(res, "64"); 104 | }); 105 | 106 | test("testNestedOutOfOrderVarsWithOperators", () { 107 | Expression e = new Expression("a+y"); 108 | 109 | e.addOperator(OperatorImpl(">>", 30, true, fEval: (v1, v2) { 110 | return Decimal.parse("12345.678"); 111 | })); 112 | 113 | e.setStringVariable("b", "123.45678"); // e=((123.45678 >> 2))+2+y 114 | e.setStringVariable("x", "b >> 2"); // e=((b >> 2)+2)+y 115 | e.setStringVariable("a", "x+2"); // e=(x+2)+y 116 | e.setStringVariable( 117 | "y", "5"); // e=((123.45678>>2)+2)+5 = 12345.678+2+5 = 12352.678 118 | expect(e.eval().toString(), "12352.678"); 119 | }); 120 | } 121 | -------------------------------------------------------------------------------- /test/variables_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:decimal/decimal.dart'; 28 | import 'package:eval_ex/expression.dart'; 29 | import 'package:test/test.dart'; 30 | 31 | void main() { 32 | test("testVars", () { 33 | expect(Expression("PI").eval()?.toStringAsFixed(7), "3.1415927"); 34 | expect(Expression("PI").eval()?.toString(), 35 | "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679"); 36 | expect(Expression("PI").eval()?.toStringAsFixed(34), 37 | "3.1415926535897932384626433832795029"); 38 | expect(Expression("PI").eval()?.toStringAsFixed(16), "3.1415926535897932"); 39 | expect(Expression("PI").eval()?.toStringAsFixed(7), "3.1415927"); 40 | expect(Expression("PI*2.0").eval()?.toStringAsFixed(7), "6.2831853"); 41 | expect( 42 | (Expression("3*x")..setDecimalVariable("x", Decimal.fromInt(7))) 43 | .eval() 44 | .toString(), 45 | "21"); 46 | expect( 47 | (Expression("(a^2)+(b^2)") 48 | ..setDecimalVariable("a", Decimal.fromInt(2)) 49 | ..setDecimalVariable("b", Decimal.fromInt(4))) 50 | .eval() 51 | .toString(), 52 | "20"); 53 | expect( 54 | (Expression("a^(2+b)^2") 55 | ..setStringVariable("a", "2") 56 | ..setStringVariable("b", "4")) 57 | .eval() 58 | .toString(), 59 | "68719476736"); 60 | }); 61 | 62 | test("testSubstitution", () { 63 | Expression e = new Expression("x+y"); 64 | 65 | expect( 66 | (e 67 | ..setStringVariable("x", "1") 68 | ..setStringVariable("y", "1")) 69 | .eval() 70 | .toString(), 71 | "2"); 72 | expect((e..setStringVariable("y", "0")).eval().toString(), "1"); 73 | expect((e..setStringVariable("x", "0")).eval().toString(), "0"); 74 | }); 75 | 76 | test("testWith", () { 77 | expect( 78 | (Expression("3*x")..setDecimalVariable("x", Decimal.fromInt(7))) 79 | .eval() 80 | .toString(), 81 | "21"); 82 | 83 | expect( 84 | (Expression("(a^2)+(b^2)") 85 | ..setDecimalVariable("a", Decimal.fromInt(2)) 86 | ..setDecimalVariable("b", Decimal.fromInt(4))) 87 | .eval() 88 | .toString(), 89 | "20"); 90 | 91 | expect( 92 | (Expression("a^(2+b)^2") 93 | ..setStringVariable("a", "2") 94 | ..setStringVariable("b", "4")) 95 | .eval() 96 | .toString(), 97 | "68719476736"); 98 | 99 | expect( 100 | (Expression("_a^(2+_b)^2") 101 | ..setStringVariable("_a", "2") 102 | ..setStringVariable("_b", "4")) 103 | .eval() 104 | .toString(), 105 | "68719476736"); 106 | }); 107 | 108 | test("testNames", () { 109 | expect( 110 | (Expression("3*longname") 111 | ..setDecimalVariable("longname", Decimal.fromInt(7))) 112 | .eval() 113 | .toString(), 114 | "21"); 115 | 116 | expect( 117 | (Expression("3*longname1") 118 | ..setDecimalVariable("longname1", Decimal.fromInt(7))) 119 | .eval() 120 | .toString(), 121 | "21"); 122 | 123 | expect( 124 | (Expression("3*_longname1") 125 | ..setDecimalVariable("_longname1", Decimal.fromInt(7))) 126 | .eval() 127 | .toString(), 128 | "21"); 129 | }); 130 | 131 | test("failsIfVariableDoesNotExist", () { 132 | expect(() => Expression("3*unknown").eval(), 133 | throwsA(isA())); 134 | }); 135 | 136 | test("testNullVariable", () { 137 | Expression e; 138 | e = new Expression("a")..setStringVariable("a", "null"); 139 | expect(e.eval(), null); 140 | 141 | e = new Expression("a")..setDecimalVariable("a", null); 142 | expect(e.eval(), null); 143 | 144 | String err = ""; 145 | 146 | try { 147 | new Expression("a+1")..setStringVariable("a", "null").eval(); 148 | } on AssertionError catch (e) { 149 | err = e.message.toString(); 150 | } 151 | 152 | expect(err, "First operand may not be null."); 153 | }); 154 | } 155 | -------------------------------------------------------------------------------- /test/booleans_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:eval_ex/expression.dart'; 28 | import 'package:test/test.dart'; 29 | 30 | void assertToken(String surface, TokenType type, Token actual) { 31 | expect(actual.surface, surface); 32 | expect(actual.type, type); 33 | } 34 | 35 | void main() { 36 | test('testIsBoolean', () { 37 | Expression e = Expression("1==1"); 38 | expect(e.isBoolean(), true); 39 | 40 | e = Expression("true==TRUE"); 41 | expect(e.eval().toString(), "1"); 42 | 43 | e = Expression("false==FALSE"); 44 | expect(e.eval().toString(), "1"); 45 | 46 | e = Expression("a==b") 47 | ..setStringVariable("a", "1") 48 | ..setStringVariable("b", "2"); 49 | expect(e.isBoolean(), true); 50 | 51 | e = new Expression("(1==1)||(c==a+b)"); 52 | expect(e.isBoolean(), true); 53 | 54 | e = new Expression("(z+z==x-y)||(c==a+b)"); 55 | expect(e.isBoolean(), true); 56 | 57 | e = new Expression("NOT(a+b)"); 58 | expect(e.isBoolean(), true); 59 | 60 | e = new Expression("a+b"); 61 | expect(e.isBoolean(), false); 62 | 63 | e = new Expression("(a==b)+(b==c)"); 64 | expect(e.isBoolean(), false); 65 | 66 | e = new Expression("SQRT(2)"); 67 | expect(e.isBoolean(), false); 68 | 69 | e = new Expression("SQRT(2) == SQRT(b)"); 70 | expect(e.isBoolean(), true); 71 | 72 | e = new Expression("IF(a==b,x+y,x-y)"); 73 | expect(e.isBoolean(), false); 74 | 75 | e = new Expression("IF(a==b,x==y,a==b)"); 76 | expect(e.isBoolean(), true); 77 | 78 | e = new Expression("5 < 10"); 79 | expect(e.isBoolean(), true); 80 | 81 | e = new Expression("50 > 10"); 82 | expect(e.isBoolean(), true); 83 | 84 | e = new Expression("5 <= 10"); 85 | expect(e.isBoolean(), true); 86 | 87 | e = new Expression("5 >= 10"); 88 | expect(e.isBoolean(), true); 89 | }); 90 | 91 | test('testAndTokenizer', () { 92 | Expression e = new Expression("1&&0"); 93 | Iterator i = e.getExpressionTokenizer(); 94 | 95 | i.moveNext(); 96 | assertToken("1", TokenType.literal, i.current!); 97 | i.moveNext(); 98 | assertToken("&&", TokenType.operator, i.current!); 99 | i.moveNext(); 100 | assertToken("0", TokenType.literal, i.current!); 101 | }); 102 | 103 | test('testAndRPN', () { 104 | expect(Expression("1&&0").toRPN(), "1 0 &&"); 105 | }); 106 | 107 | test('testAndEval', () { 108 | expect(Expression("1&&0").eval().toString(), "0"); 109 | expect(Expression("1&&1").eval().toString(), "1"); 110 | expect(Expression("0&&0").eval().toString(), "0"); 111 | expect(Expression("0&&1").eval().toString(), "0"); 112 | }); 113 | 114 | test('testOrEval', () { 115 | expect(Expression("1||0").eval().toString(), "1"); 116 | expect(Expression("1||1").eval().toString(), "1"); 117 | expect(Expression("0||0").eval().toString(), "0"); 118 | expect(Expression("0||1").eval().toString(), "1"); 119 | }); 120 | 121 | test('testCompare', () { 122 | expect(Expression("2>1").eval().toString(), "1"); 123 | expect(Expression("2<1").eval().toString(), "0"); 124 | expect(Expression("1>2").eval().toString(), "0"); 125 | expect(Expression("1<2").eval().toString(), "1"); 126 | expect(Expression("1=2").eval().toString(), "0"); 127 | expect(Expression("1=1").eval().toString(), "1"); 128 | expect(Expression("1>=1").eval().toString(), "1"); 129 | expect(Expression("1.1>=1").eval().toString(), "1"); 130 | expect(Expression("1>=2").eval().toString(), "0"); 131 | expect(Expression("1<=1").eval().toString(), "1"); 132 | expect(Expression("1.1<=1").eval().toString(), "0"); 133 | expect(Expression("1<=2").eval().toString(), "1"); 134 | expect(Expression("1=2").eval().toString(), "0"); 135 | expect(Expression("1=1").eval().toString(), "1"); 136 | expect(Expression("1!=2").eval().toString(), "1"); 137 | expect(Expression("1!=1").eval().toString(), "0"); 138 | expect(Expression("16>10").eval().toString(), "1"); 139 | expect(Expression("16>10").eval().toString(), "1"); 140 | expect(Expression("10.3>10").eval().toString(), "1"); 141 | }); 142 | 143 | test('testCompareCombined', () { 144 | expect(Expression("(2>1)||(1=0)").eval().toString(), "1"); 145 | expect(Expression("(2>3)||(1=0)").eval().toString(), "0"); 146 | expect(Expression("(2>3)||(1=0)||(1&&1)").eval().toString(), "1"); 147 | }); 148 | 149 | test('testMixed', () { 150 | expect(Expression("1.5 * 7 = 3").eval().toString(), "0"); 151 | expect(Expression("1.5 * 7 = 10.5").eval().toString(), "1"); 152 | }); 153 | 154 | test('testNot', () { 155 | expect(Expression("not(1)").eval().toString(), "0"); 156 | expect(Expression("not(0)").eval().toString(), "1"); 157 | expect(Expression("not(1.5 * 7 = 3)").eval().toString(), "1"); 158 | expect(Expression("not(1.5 * 7 = 10.5)").eval().toString(), "0"); 159 | }); 160 | 161 | test('testConstants', () { 162 | expect(Expression("TRUE!=FALSE").eval().toString(), "1"); 163 | expect(Expression("TRUE==2").eval().toString(), "0"); 164 | expect(Expression("NOT(TRUE)==FALSE").eval().toString(), "1"); 165 | expect(Expression("NOT(FALSE)==TRUE").eval().toString(), "1"); 166 | expect(Expression("TRUE && FALSE").eval().toString(), "0"); 167 | expect(Expression("TRUE || FALSE").eval().toString(), "1"); 168 | }); 169 | 170 | test('testIf', () { 171 | expect(Expression("if(TRUE, 5, 3)").eval().toString(), "5"); 172 | expect(Expression("IF(FALSE, 5, 3)").eval().toString(), "3"); 173 | expect(Expression("If(2, 5.35, 3)").eval().toString(), "5.35"); 174 | }); 175 | 176 | test("testDecimals", () { 177 | expect(Expression("if(0.0, 1, 0)").eval().toString(), "0"); 178 | expect(Expression("0.0 || 0.0").eval().toString(), "0"); 179 | expect(Expression("not(0.0)").eval().toString(), "1"); 180 | expect(Expression("0.0 && 0.0").eval().toString(), "0"); 181 | }); 182 | } 183 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eval_ex 2 | 3 | A Dart port of https://github.com/uklimaschewski/EvalEx. 4 | EvalEx supports mathematical and boolean expression evaluation, using arbitrary precision. 5 | EvalEx supports functions, variables, and operators and additionally makes it very easy to 6 | add your own custom functions or operators. 7 | 8 | EvalEx uses [decimal](https://pub.dev/packages/decimal) to support arbitrary precision arithmetic. 9 | View package on [pub.dev](https://pub.dev/packages/eval_ex). 10 | 11 | ## Basic example 12 | 13 | ```dart 14 | void main() { 15 | Expression exp = Expression("2 ^ 4 + 8"); 16 | print(exp.eval().toString()); // 24 17 | 18 | // With a variable 19 | exp = Expression("a ^ b + c"); 20 | // Variables may contain alphanumeric characters, and "_". This can be changed 21 | // by using setVariableCharacters(..) and setFirstVariableCharacters(..) (what chars variable are allowed to start with). 22 | exp.setStringVariable("a", "2"); 23 | exp.setStringVariable("b", "4"); 24 | exp.setStringVariable("c", "8"); 25 | print(exp.eval().toString()); // 24 26 | 27 | // With a function 28 | exp = Expression("MAX(-7,8)"); 29 | print(exp.eval().toString()); // 8 30 | 31 | // Boolean logic 32 | exp = Expression("1>0 && 5 == 5"); 33 | print(exp.eval().toString()); // 1 34 | 35 | exp = Expression("1>0 && 5 == 4"); 36 | print(exp.eval().toString()); // 0 37 | } 38 | 39 | ``` 40 | 41 | ## Built-in functions and operators 42 | | Function | Description | 43 | |----------------------|-----------------------------------------------------------------------------------| 44 | | + | Addition | 45 | | - | Subtraction | 46 | | * | Multiplication | 47 | | / | Division | 48 | | % | Modulus | 49 | | ^ | Power | 50 | | && | Returns 1 if both expressions are true, 0 otherwise | 51 | | \|\| | Returns 1 if either or both expressions are true, 0 otherwise | 52 | | > | Returns 1 if the left expression is greater than the right | 53 | | >= | Returns 1 if the left expression is greater than or equal to the right | 54 | | < | Returns 1 if the right expression is greater than the left | 55 | | <= | Returns 1 if the right expression is greater than or equal to the left | 56 | | = | Returns 1 if the left and right expressions are equal | 57 | | == | Returns 1 if the left and right expressions are equal | 58 | | != | Returns 1 if the left and right expressions are NOT equal | 59 | | <> | Returns 1 if the left and right expressions are NOT equal | 60 | | STREQ("str1","str2") | Returns 1 if the literal "str1" is equal to "str2", otherwise returns 0 | 61 | | FACT(int) | Computes the factorial of arg1 | 62 | | NOT(expression) | Returns 1 if arg1 evaluates to 0, otherwise returns 0 | 63 | | IF(cond,exp1,exp2) | Returns exp1 if cond evaluates to 1, otherwise returns exp2 | 64 | | Random() | Returns a random decimal between 0 and 1 | 65 | | SINR(exp) | Evaluates the SIN of exp, assuming exp is in radians | 66 | | COSR(exp) | Evaluates the COS of exp, assuming exp is in radians | 67 | | TANR(exp) | Evaluates the TAN of exp, assuming exp is in radians | 68 | | COTR(exp) | Evaluates the COT of exp, assuming exp is in radians | 69 | | SECR(exp) | Evaluates the SEC of exp, assuming exp is in radians | 70 | | CSCR(exp) | Evaluates the CSC of exp, assuming exp is in radians | 71 | | SIN(exp) | Evaluates the SIN of exp, assuming exp is in degrees | 72 | | COS(exp) | Evaluates the COS of exp, assuming exp is in degrees | 73 | | TAN(exp) | Evaluates the TAN of exp, assuming exp is in degrees | 74 | | COT(exp) | Evaluates the COT of exp, assuming exp is in degrees | 75 | | SEC(exp) | Evaluates the SEC of exp, assuming exp is in degrees | 76 | | CSC(exp) | Evaluates the CSC of exp, assuming exp is in degrees | 77 | | ASINR(exp) | Evaluates the ARCSIN of exp, assuming exp is in radians | 78 | | ACOSR(exp) | Evaluates the ARCCOS of exp, assuming exp is in radians | 79 | | ATANR(exp) | Evaluates the ARCTAN of exp, assuming exp is in radians | 80 | | ACOTR(exp) | Evaluates the ARCCOT of exp, assuming exp is in radians | 81 | | ATAN2R(exp1, exp1) | Evaluates the ARCTAN between exp1 and exp2, assuming exp1 and exp2 are in radians | 82 | | ASIN(exp) | Evaluates the ARCSIN of exp, assuming exp is in degrees | 83 | | ACOS(exp) | Evaluates the ARCCOS of exp, assuming exp is in degrees | 84 | | ATAN(exp) | Evaluates the ARCTAN of exp, assuming exp is in degrees | 85 | | ACOT(exp) | Evaluates the ARCCOT of exp, assuming exp is in degrees | 86 | | ATAN2(exp1, exp1) | Evaluates the ARCTAN between exp1 and exp2, assuming exp1 and exp2 are in degrees | 87 | | RAD(deg) | Converts deg to radians | 88 | | DEG(rad) | Converts rad to degrees | 89 | | MAX(a,b,...) | Returns the maximum value from the provided list of 1 or more expressions | 90 | | MIN(a,b,...) | Returns the minimum value from the provided list of 1 or more expressions | 91 | | ABS(exp) | Returns the absolute value of exp | 92 | | LOG(exp) | Returns the natural logarithm of exp | 93 | | LOG10(exp) | Returns the log base 10 of exp | 94 | | ROUND(exp,precision) | Returns exp rounded to precision decimal points | 95 | | FLOOR(exp) | Returns the floor of exp | 96 | | CEILING(exp) | Returns the ceiling of exp | 97 | | SQRT(exp) | Computes the square root of exp | 98 | | e | Euler's number | 99 | | PI | Ratio of circle's circumference to diameter | 100 | | NULL | Alias for null | 101 | | TRUE or true | Alias for 1 | 102 | | FALSE or false | Alias for 0 | 103 | 104 | ## Adding custom functions, operators, and variables 105 | Custom functions, operators, and variables can be defined by adding them in built_ins.dart 106 | 107 | ## Differences from https://github.com/uklimaschewski/EvalEx. 108 | - Hyperbolic functions aren't supported 109 | - The SQRT function is currently limited to 64-bit precision 110 | - Built in operators/functions/variables are instead defined in built_ins.dart 111 | -------------------------------------------------------------------------------- /test/customs_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'dart:math'; 28 | 29 | import 'package:decimal/decimal.dart'; 30 | import 'package:eval_ex/abstract_function.dart'; 31 | import 'package:eval_ex/abstract_lazy_function.dart'; 32 | import 'package:eval_ex/abstract_operator.dart'; 33 | import 'package:eval_ex/built_ins.dart'; 34 | import 'package:eval_ex/expression.dart'; 35 | import 'package:test/test.dart'; 36 | 37 | void main() { 38 | test("testCustomOperator", () { 39 | Expression e = new Expression("2.1234 >> 2"); 40 | 41 | e.addOperator(OperatorImpl(">>", 30, true, fEval: (v1, v2) { 42 | return Decimal.parse(pow(10, v2.toBigInt().toInt()).toString()) * v1; 43 | })); 44 | 45 | expect(e.eval().toString(), "212.34"); 46 | }); 47 | 48 | test("testCustomFunction", () { 49 | Expression e = new Expression("2 * average(12,4,8)"); 50 | 51 | e.addFunc(AbstractFuncImpl("average", 3, fEval: (params) { 52 | Decimal sum = params[0] + params[1] + params[2]; 53 | return (sum / Decimal.fromInt(3)).toDecimal(); 54 | })); 55 | 56 | expect(e.eval().toString(), "16"); 57 | }); 58 | 59 | test("testCustomFunctionInstanceClass", () { 60 | Expression e = new Expression("2 * average(12,4,8)"); 61 | 62 | e.addFunc(FunctionImpl("average", 3, fEval: (params) { 63 | Decimal sum = params[0] + params[1] + params[2]; 64 | return (sum / Decimal.fromInt(3)).toDecimal(); 65 | })); 66 | 67 | expect(e.eval().toString(), "16"); 68 | }); 69 | 70 | test("testCustomFunctionVariableParameters", () { 71 | Expression e = new Expression("2 * average(12,4,8,2,9)"); 72 | e.addFunc(AbstractFuncImpl("average", -1, fEval: (params) { 73 | Decimal sum = Decimal.zero; 74 | for (Decimal param in params) { 75 | sum = sum + param; 76 | } 77 | return (sum / Decimal.fromInt(params.length)).toDecimal(); 78 | })); 79 | 80 | expect(e.eval().toString(), "14"); 81 | }); 82 | 83 | test("testCustomFunctionVariableParametersInstanceClass", () { 84 | Expression e = new Expression("2 * average(12,4,8,2,9)"); 85 | e.addFunc(FunctionImpl("average", -1, fEval: (params) { 86 | Decimal sum = Decimal.zero; 87 | for (Decimal param in params) { 88 | sum = sum + param; 89 | } 90 | return (sum / Decimal.fromInt(params.length)).toDecimal(); 91 | })); 92 | 93 | expect(e.eval().toString(), "14"); 94 | }); 95 | 96 | test("testCustomFunctionStringParameters", () { 97 | Expression e = new Expression("STREQ(\"test\", \"test2\")"); 98 | e.addLazyFunction(LazyFunctionImpl("STREQ", 2, fEval: (params) { 99 | if (params[0].getString() == params[1].getString()) { 100 | return LazyNumberImpl(eval: () => Decimal.zero, getString: () => "0"); 101 | } 102 | 103 | return LazyNumberImpl(eval: () => Decimal.one, getString: () => "1"); 104 | })); 105 | 106 | expect(e.eval().toString(), "1"); 107 | }); 108 | 109 | test("testCustomFunctionBoolean", () { 110 | Expression e = new Expression("STREQ(\"test\", \"test2\")"); 111 | e.addLazyFunction( 112 | LazyFunctionImpl("STREQ", 2, booleanFunction: true, fEval: (params) { 113 | if (params[0].getString() == params[1].getString()) { 114 | return LazyNumberImpl(eval: () => Decimal.zero, getString: () => "0"); 115 | } 116 | 117 | return LazyNumberImpl(eval: () => Decimal.one, getString: () => "1"); 118 | })); 119 | 120 | expect(e.eval().toString(), "1"); 121 | expect(e.isBoolean(), true); 122 | }); 123 | 124 | test("testUnary", () { 125 | Expression exp = new Expression("~23"); 126 | 127 | exp.addOperator(UnaryOperatorImpl("~", 60, false, fEval: (d) { 128 | return d; 129 | })); 130 | 131 | expect(exp.eval().toString(), "23"); 132 | }); 133 | 134 | test("testCustomOperatorAnd", () { 135 | Expression e = new Expression("1 == 1 AND 2 == 2"); 136 | 137 | e.addOperator(OperatorImpl("AND", Expression.operatorPrecedenceAnd, false, 138 | booleanOperator: true, fEval: (v1, v2) { 139 | bool b1 = v1.compareTo(Decimal.zero) != 0; 140 | 141 | if (!b1) { 142 | return Decimal.zero; 143 | } 144 | 145 | bool b2 = v2.compareTo(Decimal.zero) != 0; 146 | return b2 ? Decimal.one : Decimal.zero; 147 | })); 148 | 149 | expect(e.eval().toString(), "1"); 150 | }); 151 | 152 | test("testCustomFunctionWithStringParams", () { 153 | Expression e = new Expression("INDEXOF(STRING1, STRING2)"); 154 | 155 | e.addLazyFunction(new AbstractLazyFuncImpl("INDEXOF", 2, fEval: (params) { 156 | String st1 = params[0].getString(); 157 | String st2 = params[1].getString(); 158 | int index = st1.indexOf(st2); 159 | 160 | return new LazyNumberImpl(eval: () { 161 | return Decimal.fromInt(index); 162 | }, getString: () { 163 | return index.toString(); 164 | }); 165 | })); 166 | 167 | e.setStringVariable("STRING1", "The quick brown fox"); 168 | e.setStringVariable("STRING2", "The"); 169 | 170 | expect("0", e.eval().toString()); 171 | 172 | e.setStringVariable("STRING1", "The quick brown fox"); 173 | e.setStringVariable("STRING2", "brown"); 174 | 175 | expect("10", e.eval().toString()); 176 | }); 177 | 178 | test("testUnaryPostfix", () { 179 | Expression exp = new Expression("1+4!"); 180 | 181 | exp.addOperator(new AbstractOperatorImpl("!", 64, true, unaryOperator: true, 182 | fEval: (v1, v2) { 183 | if (v1.toDouble() > 50) { 184 | throw new ExpressionException("Operand must be <= 50"); 185 | } 186 | 187 | int number = v1.toBigInt().toInt(); 188 | 189 | Decimal factorial = Decimal.one; 190 | for (int i = 1; i <= number; i++) { 191 | factorial = factorial * Decimal.fromInt(i); 192 | } 193 | return factorial; 194 | })); 195 | 196 | expect(exp.eval().toString(), "25"); 197 | }); 198 | } 199 | 200 | class AbstractOperatorImpl extends AbstractOperator { 201 | Function(Decimal v1, Decimal? v2) fEval; 202 | 203 | AbstractOperatorImpl(String oper, int precedence, bool leftAssoc, 204 | {bool booleanOperator = false, 205 | bool unaryOperator = false, 206 | required this.fEval}) 207 | : super(oper, precedence, leftAssoc, 208 | booleanOperator: booleanOperator, unaryOperator: unaryOperator); 209 | 210 | @override 211 | Decimal eval(Decimal? v1, Decimal? v2) { 212 | if (v1 == null) { 213 | throw new AssertionError("First operand may not be null."); 214 | } 215 | 216 | return fEval(v1, v2); 217 | } 218 | } 219 | 220 | class AbstractLazyFuncImpl extends AbstractLazyFunction { 221 | Function(List) fEval; 222 | 223 | AbstractLazyFuncImpl(String name, int numParams, 224 | {bool booleanFunction = false, required this.fEval}) 225 | : super(name, numParams, booleanFunction: booleanFunction); 226 | 227 | @override 228 | LazyNumber lazyEval(List lazyParams) { 229 | return fEval(lazyParams.map((d) => d!).toList()); 230 | } 231 | } 232 | 233 | class AbstractFuncImpl extends AbstractFunction { 234 | Function(List) fEval; 235 | 236 | AbstractFuncImpl(String name, int numParams, 237 | {bool booleanFunction = false, required this.fEval}) 238 | : super(name, numParams, booleanFunction: booleanFunction); 239 | 240 | @override 241 | Decimal eval(List parameters) { 242 | return this.fEval(parameters.map((d) => d!).toList()); 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | sha256: "03f6da266a27a4538a69295ec142cb5717d7d4e5727b84658b63e1e1509bac9c" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "79.0.0" 12 | _macros: 13 | dependency: transitive 14 | description: dart 15 | source: sdk 16 | version: "0.3.3" 17 | analyzer: 18 | dependency: transitive 19 | description: 20 | name: analyzer 21 | sha256: c9040fc56483c22a5e04a9f6a251313118b1a3c42423770623128fa484115643 22 | url: "https://pub.dev" 23 | source: hosted 24 | version: "7.2.0" 25 | args: 26 | dependency: transitive 27 | description: 28 | name: args 29 | sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 30 | url: "https://pub.dev" 31 | source: hosted 32 | version: "2.6.0" 33 | async: 34 | dependency: transitive 35 | description: 36 | name: async 37 | sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 38 | url: "https://pub.dev" 39 | source: hosted 40 | version: "2.12.0" 41 | boolean_selector: 42 | dependency: transitive 43 | description: 44 | name: boolean_selector 45 | sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" 46 | url: "https://pub.dev" 47 | source: hosted 48 | version: "2.1.2" 49 | clock: 50 | dependency: transitive 51 | description: 52 | name: clock 53 | sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b 54 | url: "https://pub.dev" 55 | source: hosted 56 | version: "1.1.2" 57 | collection: 58 | dependency: transitive 59 | description: 60 | name: collection 61 | sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" 62 | url: "https://pub.dev" 63 | source: hosted 64 | version: "1.19.1" 65 | convert: 66 | dependency: transitive 67 | description: 68 | name: convert 69 | sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 70 | url: "https://pub.dev" 71 | source: hosted 72 | version: "3.1.2" 73 | coverage: 74 | dependency: transitive 75 | description: 76 | name: coverage 77 | sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43 78 | url: "https://pub.dev" 79 | source: hosted 80 | version: "1.11.1" 81 | crypto: 82 | dependency: transitive 83 | description: 84 | name: crypto 85 | sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" 86 | url: "https://pub.dev" 87 | source: hosted 88 | version: "3.0.6" 89 | dart_numerics: 90 | dependency: "direct main" 91 | description: 92 | name: dart_numerics 93 | sha256: "47408d4890551636204851325e5649bf1a1616ebc325184c36722a1716cbaba4" 94 | url: "https://pub.dev" 95 | source: hosted 96 | version: "0.0.6" 97 | decimal: 98 | dependency: "direct main" 99 | description: 100 | name: decimal 101 | sha256: da8f65df568345f2738cc8b0de74971c86d2d93ce5fc8c4ec094f6b7c5d48eb5 102 | url: "https://pub.dev" 103 | source: hosted 104 | version: "3.1.0" 105 | file: 106 | dependency: transitive 107 | description: 108 | name: file 109 | sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 110 | url: "https://pub.dev" 111 | source: hosted 112 | version: "7.0.1" 113 | frontend_server_client: 114 | dependency: transitive 115 | description: 116 | name: frontend_server_client 117 | sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 118 | url: "https://pub.dev" 119 | source: hosted 120 | version: "4.0.0" 121 | glob: 122 | dependency: transitive 123 | description: 124 | name: glob 125 | sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" 126 | url: "https://pub.dev" 127 | source: hosted 128 | version: "2.1.2" 129 | http: 130 | dependency: transitive 131 | description: 132 | name: http 133 | sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 134 | url: "https://pub.dev" 135 | source: hosted 136 | version: "1.2.2" 137 | http_multi_server: 138 | dependency: transitive 139 | description: 140 | name: http_multi_server 141 | sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 142 | url: "https://pub.dev" 143 | source: hosted 144 | version: "3.2.2" 145 | http_parser: 146 | dependency: transitive 147 | description: 148 | name: http_parser 149 | sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" 150 | url: "https://pub.dev" 151 | source: hosted 152 | version: "4.1.2" 153 | intl: 154 | dependency: transitive 155 | description: 156 | name: intl 157 | sha256: "00f33b908655e606b86d2ade4710a231b802eec6f11e87e4ea3783fd72077a50" 158 | url: "https://pub.dev" 159 | source: hosted 160 | version: "0.20.1" 161 | io: 162 | dependency: transitive 163 | description: 164 | name: io 165 | sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b 166 | url: "https://pub.dev" 167 | source: hosted 168 | version: "1.0.5" 169 | js: 170 | dependency: transitive 171 | description: 172 | name: js 173 | sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf 174 | url: "https://pub.dev" 175 | source: hosted 176 | version: "0.7.1" 177 | logging: 178 | dependency: transitive 179 | description: 180 | name: logging 181 | sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 182 | url: "https://pub.dev" 183 | source: hosted 184 | version: "1.3.0" 185 | macros: 186 | dependency: transitive 187 | description: 188 | name: macros 189 | sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" 190 | url: "https://pub.dev" 191 | source: hosted 192 | version: "0.1.3-main.0" 193 | matcher: 194 | dependency: transitive 195 | description: 196 | name: matcher 197 | sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 198 | url: "https://pub.dev" 199 | source: hosted 200 | version: "0.12.17" 201 | meta: 202 | dependency: transitive 203 | description: 204 | name: meta 205 | sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c 206 | url: "https://pub.dev" 207 | source: hosted 208 | version: "1.16.0" 209 | mime: 210 | dependency: transitive 211 | description: 212 | name: mime 213 | sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" 214 | url: "https://pub.dev" 215 | source: hosted 216 | version: "2.0.0" 217 | node_preamble: 218 | dependency: transitive 219 | description: 220 | name: node_preamble 221 | sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" 222 | url: "https://pub.dev" 223 | source: hosted 224 | version: "2.0.2" 225 | package_config: 226 | dependency: transitive 227 | description: 228 | name: package_config 229 | sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" 230 | url: "https://pub.dev" 231 | source: hosted 232 | version: "2.1.1" 233 | path: 234 | dependency: transitive 235 | description: 236 | name: path 237 | sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" 238 | url: "https://pub.dev" 239 | source: hosted 240 | version: "1.9.1" 241 | pool: 242 | dependency: transitive 243 | description: 244 | name: pool 245 | sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" 246 | url: "https://pub.dev" 247 | source: hosted 248 | version: "1.5.1" 249 | pub_semver: 250 | dependency: transitive 251 | description: 252 | name: pub_semver 253 | sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" 254 | url: "https://pub.dev" 255 | source: hosted 256 | version: "2.1.5" 257 | rational: 258 | dependency: transitive 259 | description: 260 | name: rational 261 | sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336 262 | url: "https://pub.dev" 263 | source: hosted 264 | version: "2.2.3" 265 | shelf: 266 | dependency: transitive 267 | description: 268 | name: shelf 269 | sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 270 | url: "https://pub.dev" 271 | source: hosted 272 | version: "1.4.2" 273 | shelf_packages_handler: 274 | dependency: transitive 275 | description: 276 | name: shelf_packages_handler 277 | sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" 278 | url: "https://pub.dev" 279 | source: hosted 280 | version: "3.0.2" 281 | shelf_static: 282 | dependency: transitive 283 | description: 284 | name: shelf_static 285 | sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 286 | url: "https://pub.dev" 287 | source: hosted 288 | version: "1.1.3" 289 | shelf_web_socket: 290 | dependency: transitive 291 | description: 292 | name: shelf_web_socket 293 | sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 294 | url: "https://pub.dev" 295 | source: hosted 296 | version: "2.0.1" 297 | source_map_stack_trace: 298 | dependency: transitive 299 | description: 300 | name: source_map_stack_trace 301 | sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b 302 | url: "https://pub.dev" 303 | source: hosted 304 | version: "2.1.2" 305 | source_maps: 306 | dependency: transitive 307 | description: 308 | name: source_maps 309 | sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" 310 | url: "https://pub.dev" 311 | source: hosted 312 | version: "0.10.13" 313 | source_span: 314 | dependency: transitive 315 | description: 316 | name: source_span 317 | sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" 318 | url: "https://pub.dev" 319 | source: hosted 320 | version: "1.10.1" 321 | stack_trace: 322 | dependency: transitive 323 | description: 324 | name: stack_trace 325 | sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" 326 | url: "https://pub.dev" 327 | source: hosted 328 | version: "1.12.1" 329 | stream_channel: 330 | dependency: transitive 331 | description: 332 | name: stream_channel 333 | sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" 334 | url: "https://pub.dev" 335 | source: hosted 336 | version: "2.1.4" 337 | string_scanner: 338 | dependency: transitive 339 | description: 340 | name: string_scanner 341 | sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" 342 | url: "https://pub.dev" 343 | source: hosted 344 | version: "1.4.1" 345 | term_glyph: 346 | dependency: transitive 347 | description: 348 | name: term_glyph 349 | sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" 350 | url: "https://pub.dev" 351 | source: hosted 352 | version: "1.2.2" 353 | test: 354 | dependency: "direct dev" 355 | description: 356 | name: test 357 | sha256: "8391fbe68d520daf2314121764d38e37f934c02fd7301ad18307bd93bd6b725d" 358 | url: "https://pub.dev" 359 | source: hosted 360 | version: "1.25.14" 361 | test_api: 362 | dependency: transitive 363 | description: 364 | name: test_api 365 | sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd 366 | url: "https://pub.dev" 367 | source: hosted 368 | version: "0.7.4" 369 | test_core: 370 | dependency: transitive 371 | description: 372 | name: test_core 373 | sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" 374 | url: "https://pub.dev" 375 | source: hosted 376 | version: "0.6.8" 377 | tuple: 378 | dependency: transitive 379 | description: 380 | name: tuple 381 | sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 382 | url: "https://pub.dev" 383 | source: hosted 384 | version: "2.0.2" 385 | typed_data: 386 | dependency: transitive 387 | description: 388 | name: typed_data 389 | sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 390 | url: "https://pub.dev" 391 | source: hosted 392 | version: "1.4.0" 393 | vm_service: 394 | dependency: transitive 395 | description: 396 | name: vm_service 397 | sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 398 | url: "https://pub.dev" 399 | source: hosted 400 | version: "15.0.0" 401 | watcher: 402 | dependency: transitive 403 | description: 404 | name: watcher 405 | sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" 406 | url: "https://pub.dev" 407 | source: hosted 408 | version: "1.1.1" 409 | web: 410 | dependency: transitive 411 | description: 412 | name: web 413 | sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb 414 | url: "https://pub.dev" 415 | source: hosted 416 | version: "1.1.0" 417 | web_socket: 418 | dependency: transitive 419 | description: 420 | name: web_socket 421 | sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" 422 | url: "https://pub.dev" 423 | source: hosted 424 | version: "0.1.6" 425 | web_socket_channel: 426 | dependency: transitive 427 | description: 428 | name: web_socket_channel 429 | sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" 430 | url: "https://pub.dev" 431 | source: hosted 432 | version: "3.0.1" 433 | webkit_inspection_protocol: 434 | dependency: transitive 435 | description: 436 | name: webkit_inspection_protocol 437 | sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" 438 | url: "https://pub.dev" 439 | source: hosted 440 | version: "1.2.1" 441 | yaml: 442 | dependency: transitive 443 | description: 444 | name: yaml 445 | sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce 446 | url: "https://pub.dev" 447 | source: hosted 448 | version: "3.1.3" 449 | sdks: 450 | dart: ">=3.5.0 <4.0.0" 451 | -------------------------------------------------------------------------------- /test/tokenizer_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:decimal/decimal.dart'; 28 | import 'package:eval_ex/built_ins.dart'; 29 | import 'package:eval_ex/expression.dart'; 30 | import 'package:test/test.dart'; 31 | 32 | void assertToken(String surface, TokenType type, Token actual) { 33 | expect(actual.surface, surface); 34 | expect(actual.type, type); 35 | } 36 | 37 | void main() { 38 | test("testSpacesFunctions", () { 39 | Expression e; 40 | Iterator i; 41 | 42 | e = new Expression("sin (30)"); 43 | i = e.getExpressionTokenizer(); 44 | i.moveNext(); 45 | assertToken("sin", TokenType.function, i.current!); 46 | i.moveNext(); 47 | assertToken("(", TokenType.openParen, i.current!); 48 | i.moveNext(); 49 | assertToken("30", TokenType.literal, i.current!); 50 | i.moveNext(); 51 | assertToken(")", TokenType.closeParen, i.current!); 52 | expect(i.moveNext(), false); 53 | }); 54 | 55 | test("testSpacesFunctionsVariablesOperators", () { 56 | Expression e; 57 | Iterator i; 58 | 59 | e = new Expression(" sin ( 30 + x ) "); 60 | i = e.getExpressionTokenizer(); 61 | i.moveNext(); 62 | assertToken("sin", TokenType.function, i.current!); 63 | i.moveNext(); 64 | assertToken("(", TokenType.openParen, i.current!); 65 | i.moveNext(); 66 | assertToken("30", TokenType.literal, i.current!); 67 | i.moveNext(); 68 | assertToken("+", TokenType.operator, i.current!); 69 | i.moveNext(); 70 | assertToken("x", TokenType.variable, i.current!); 71 | i.moveNext(); 72 | assertToken(")", TokenType.closeParen, i.current!); 73 | expect(i.moveNext(), false); 74 | }); 75 | 76 | test("testNumbers", () { 77 | Expression e; 78 | Iterator i; 79 | 80 | e = new Expression("1"); 81 | i = e.getExpressionTokenizer(); 82 | i.moveNext(); 83 | assertToken("1", TokenType.literal, i.current!); 84 | expect(i.moveNext(), false); 85 | expect(i.current, null); 86 | 87 | e = new Expression("-1"); 88 | i = e.getExpressionTokenizer(); 89 | i.moveNext(); 90 | assertToken("-u", TokenType.unaryOperator, i.current!); 91 | i.moveNext(); 92 | assertToken("1", TokenType.literal, i.current!); 93 | i.moveNext(); 94 | expect(i.moveNext(), false); 95 | expect(i.current, null); 96 | 97 | e = new Expression("123"); 98 | i = e.getExpressionTokenizer(); 99 | 100 | i.moveNext(); 101 | assertToken("123", TokenType.literal, i.current!); 102 | expect(i.moveNext(), false); 103 | expect(i.current, null); 104 | 105 | e = new Expression("-123"); 106 | i = e.getExpressionTokenizer(); 107 | i.moveNext(); 108 | assertToken("-u", TokenType.unaryOperator, i.current!); 109 | i.moveNext(); 110 | assertToken("123", TokenType.literal, i.current!); 111 | expect(i.moveNext(), false); 112 | expect(i.current, null); 113 | 114 | e = new Expression("123.4"); 115 | i = e.getExpressionTokenizer(); 116 | i.moveNext(); 117 | assertToken("123.4", TokenType.literal, i.current!); 118 | expect(i.moveNext(), false); 119 | expect(i.current, null); 120 | 121 | e = new Expression("-123.456"); 122 | i = e.getExpressionTokenizer(); 123 | i.moveNext(); 124 | assertToken("-u", TokenType.unaryOperator, i.current!); 125 | i.moveNext(); 126 | assertToken("123.456", TokenType.literal, i.current!); 127 | expect(i.moveNext(), false); 128 | expect(i.current, null); 129 | 130 | e = new Expression(".1"); 131 | i = e.getExpressionTokenizer(); 132 | i.moveNext(); 133 | assertToken(".1", TokenType.literal, i.current!); 134 | expect(i.moveNext(), false); 135 | expect(i.current, null); 136 | 137 | e = new Expression("-.1"); 138 | i = e.getExpressionTokenizer(); 139 | i.moveNext(); 140 | assertToken("-u", TokenType.unaryOperator, i.current!); 141 | i.moveNext(); 142 | assertToken(".1", TokenType.literal, i.current!); 143 | expect(i.moveNext(), false); 144 | expect(i.current, null); 145 | }); 146 | 147 | test("testTokenizerExtraSpaces", () { 148 | Expression e = new Expression("1 "); 149 | Iterator i = e.getExpressionTokenizer(); 150 | expect(i.moveNext(), true); 151 | assertToken("1", TokenType.literal, i.current!); 152 | expect(i.moveNext(), false); 153 | expect(i.current, null); 154 | 155 | e = new Expression(" "); 156 | i = e.getExpressionTokenizer(); 157 | expect(i.moveNext(), false); 158 | expect(i.current, null); 159 | 160 | e = new Expression(" 1 "); 161 | i = e.getExpressionTokenizer(); 162 | expect(i.moveNext(), true); 163 | assertToken("1", TokenType.literal, i.current!); 164 | expect(i.moveNext(), false); 165 | expect(i.current, null); 166 | 167 | e = new Expression(" 1 + 2 "); 168 | i = e.getExpressionTokenizer(); 169 | i.moveNext(); 170 | assertToken("1", TokenType.literal, i.current!); 171 | i.moveNext(); 172 | assertToken("+", TokenType.operator, i.current!); 173 | i.moveNext(); 174 | assertToken("2", TokenType.literal, i.current!); 175 | expect(i.moveNext(), false); 176 | expect(i.current, null); 177 | }); 178 | 179 | test("testTokenizer1", () { 180 | Expression e = new Expression("1+2"); 181 | Iterator i = e.getExpressionTokenizer(); 182 | 183 | i.moveNext(); 184 | assertToken("1", TokenType.literal, i.current!); 185 | i.moveNext(); 186 | assertToken("+", TokenType.operator, i.current!); 187 | i.moveNext(); 188 | assertToken("2", TokenType.literal, i.current!); 189 | }); 190 | 191 | test("testTokenizer2", () { 192 | Expression e = new Expression("1 + 2"); 193 | Iterator i = e.getExpressionTokenizer(); 194 | 195 | i.moveNext(); 196 | assertToken("1", TokenType.literal, i.current!); 197 | i.moveNext(); 198 | assertToken("+", TokenType.operator, i.current!); 199 | i.moveNext(); 200 | assertToken("2", TokenType.literal, i.current!); 201 | }); 202 | 203 | test("testTokenizer3", () { 204 | Expression e = new Expression(" 1 + 2 "); 205 | Iterator i = e.getExpressionTokenizer(); 206 | 207 | i.moveNext(); 208 | assertToken("1", TokenType.literal, i.current!); 209 | i.moveNext(); 210 | assertToken("+", TokenType.operator, i.current!); 211 | i.moveNext(); 212 | assertToken("2", TokenType.literal, i.current!); 213 | }); 214 | 215 | test("testTokenizer4", () { 216 | Expression e = new Expression("1+2-3/4*5"); 217 | Iterator i = e.getExpressionTokenizer(); 218 | 219 | i.moveNext(); 220 | assertToken("1", TokenType.literal, i.current!); 221 | i.moveNext(); 222 | assertToken("+", TokenType.operator, i.current!); 223 | i.moveNext(); 224 | assertToken("2", TokenType.literal, i.current!); 225 | i.moveNext(); 226 | assertToken("-", TokenType.operator, i.current!); 227 | i.moveNext(); 228 | assertToken("3", TokenType.literal, i.current!); 229 | i.moveNext(); 230 | assertToken("/", TokenType.operator, i.current!); 231 | i.moveNext(); 232 | assertToken("4", TokenType.literal, i.current!); 233 | i.moveNext(); 234 | assertToken("*", TokenType.operator, i.current!); 235 | i.moveNext(); 236 | assertToken("5", TokenType.literal, i.current!); 237 | }); 238 | 239 | test("testTokenizer5", () { 240 | Expression e = new Expression("1+2.1-3.45/4.982*5.0"); 241 | Iterator i = e.getExpressionTokenizer(); 242 | 243 | i.moveNext(); 244 | assertToken("1", TokenType.literal, i.current!); 245 | i.moveNext(); 246 | assertToken("+", TokenType.operator, i.current!); 247 | i.moveNext(); 248 | assertToken("2.1", TokenType.literal, i.current!); 249 | i.moveNext(); 250 | assertToken("-", TokenType.operator, i.current!); 251 | i.moveNext(); 252 | assertToken("3.45", TokenType.literal, i.current!); 253 | i.moveNext(); 254 | assertToken("/", TokenType.operator, i.current!); 255 | i.moveNext(); 256 | assertToken("4.982", TokenType.literal, i.current!); 257 | i.moveNext(); 258 | assertToken("*", TokenType.operator, i.current!); 259 | i.moveNext(); 260 | assertToken("5.0", TokenType.literal, i.current!); 261 | i.moveNext(); 262 | }); 263 | 264 | test("testTokenizer6", () { 265 | Expression e = new Expression("-3+4*-1"); 266 | Iterator i = e.getExpressionTokenizer(); 267 | 268 | i.moveNext(); 269 | assertToken("-u", TokenType.unaryOperator, i.current!); 270 | i.moveNext(); 271 | assertToken("3", TokenType.literal, i.current!); 272 | i.moveNext(); 273 | assertToken("+", TokenType.operator, i.current!); 274 | i.moveNext(); 275 | assertToken("4", TokenType.literal, i.current!); 276 | i.moveNext(); 277 | assertToken("*", TokenType.operator, i.current!); 278 | i.moveNext(); 279 | assertToken("-u", TokenType.unaryOperator, i.current!); 280 | i.moveNext(); 281 | assertToken("1", TokenType.literal, i.current!); 282 | }); 283 | 284 | test("testTokenizer7", () { 285 | Expression e = new Expression("(-3+4)*-1/(7-(5*-8))"); 286 | Iterator i = e.getExpressionTokenizer(); 287 | 288 | i.moveNext(); 289 | assertToken("(", TokenType.openParen, i.current!); 290 | i.moveNext(); 291 | assertToken("-u", TokenType.unaryOperator, i.current!); 292 | i.moveNext(); 293 | assertToken("3", TokenType.literal, i.current!); 294 | i.moveNext(); 295 | assertToken("+", TokenType.operator, i.current!); 296 | i.moveNext(); 297 | assertToken("4", TokenType.literal, i.current!); 298 | i.moveNext(); 299 | assertToken(")", TokenType.closeParen, i.current!); 300 | 301 | i.moveNext(); 302 | assertToken("*", TokenType.operator, i.current!); 303 | i.moveNext(); 304 | assertToken("-u", TokenType.unaryOperator, i.current!); 305 | i.moveNext(); 306 | assertToken("1", TokenType.literal, i.current!); 307 | i.moveNext(); 308 | assertToken("/", TokenType.operator, i.current!); 309 | i.moveNext(); 310 | assertToken("(", TokenType.openParen, i.current!); 311 | i.moveNext(); 312 | assertToken("7", TokenType.literal, i.current!); 313 | i.moveNext(); 314 | assertToken("-", TokenType.operator, i.current!); 315 | i.moveNext(); 316 | assertToken("(", TokenType.openParen, i.current!); 317 | i.moveNext(); 318 | assertToken("5", TokenType.literal, i.current!); 319 | i.moveNext(); 320 | assertToken("*", TokenType.operator, i.current!); 321 | i.moveNext(); 322 | assertToken("-u", TokenType.unaryOperator, i.current!); 323 | i.moveNext(); 324 | assertToken("8", TokenType.literal, i.current!); 325 | i.moveNext(); 326 | assertToken(")", TokenType.closeParen, i.current!); 327 | i.moveNext(); 328 | assertToken(")", TokenType.closeParen, i.current!); 329 | }); 330 | 331 | test("testTokenizer8", () { 332 | Expression e = new Expression("(1.9+2.8)/4.7"); 333 | Iterator i = e.getExpressionTokenizer(); 334 | 335 | i.moveNext(); 336 | assertToken("(", TokenType.openParen, i.current!); 337 | i.moveNext(); 338 | assertToken("1.9", TokenType.literal, i.current!); 339 | i.moveNext(); 340 | assertToken("+", TokenType.operator, i.current!); 341 | i.moveNext(); 342 | assertToken("2.8", TokenType.literal, i.current!); 343 | i.moveNext(); 344 | assertToken(")", TokenType.closeParen, i.current!); 345 | i.moveNext(); 346 | assertToken("/", TokenType.operator, i.current!); 347 | i.moveNext(); 348 | assertToken("4.7", TokenType.literal, i.current!); 349 | }); 350 | 351 | test("testTokenizerFunction1", () { 352 | Expression e = new Expression("ABS(3.5)"); 353 | Iterator i = e.getExpressionTokenizer(); 354 | 355 | i.moveNext(); 356 | assertToken("ABS", TokenType.function, i.current!); 357 | i.moveNext(); 358 | assertToken("(", TokenType.openParen, i.current!); 359 | i.moveNext(); 360 | assertToken("3.5", TokenType.literal, i.current!); 361 | i.moveNext(); 362 | assertToken(")", TokenType.closeParen, i.current!); 363 | }); 364 | 365 | test("testTokenizerFunction2", () { 366 | Expression e = new Expression("3-ABS(3.5)/9"); 367 | Iterator i = e.getExpressionTokenizer(); 368 | 369 | i.moveNext(); 370 | assertToken("3", TokenType.literal, i.current!); 371 | i.moveNext(); 372 | assertToken("-", TokenType.operator, i.current!); 373 | i.moveNext(); 374 | assertToken("ABS", TokenType.function, i.current!); 375 | i.moveNext(); 376 | assertToken("(", TokenType.openParen, i.current!); 377 | i.moveNext(); 378 | assertToken("3.5", TokenType.literal, i.current!); 379 | i.moveNext(); 380 | assertToken(")", TokenType.closeParen, i.current!); 381 | i.moveNext(); 382 | assertToken("/", TokenType.operator, i.current!); 383 | i.moveNext(); 384 | assertToken("9", TokenType.literal, i.current!); 385 | }); 386 | 387 | test("testTokenizerFunction3", () { 388 | Expression e = new Expression("MAX(3.5,5.2)"); 389 | Iterator i = e.getExpressionTokenizer(); 390 | 391 | i.moveNext(); 392 | assertToken("MAX", TokenType.function, i.current!); 393 | i.moveNext(); 394 | assertToken("(", TokenType.openParen, i.current!); 395 | i.moveNext(); 396 | assertToken("3.5", TokenType.literal, i.current!); 397 | i.moveNext(); 398 | assertToken(",", TokenType.comma, i.current!); 399 | i.moveNext(); 400 | assertToken("5.2", TokenType.literal, i.current!); 401 | i.moveNext(); 402 | assertToken(")", TokenType.closeParen, i.current!); 403 | }); 404 | 405 | test("testTokenizerFunction4", () { 406 | Expression e = new Expression("3-MAX(3.5,5.2)/9"); 407 | Iterator i = e.getExpressionTokenizer(); 408 | 409 | i.moveNext(); 410 | assertToken("3", TokenType.literal, i.current!); 411 | i.moveNext(); 412 | assertToken("-", TokenType.operator, i.current!); 413 | i.moveNext(); 414 | assertToken("MAX", TokenType.function, i.current!); 415 | i.moveNext(); 416 | assertToken("(", TokenType.openParen, i.current!); 417 | i.moveNext(); 418 | assertToken("3.5", TokenType.literal, i.current!); 419 | i.moveNext(); 420 | assertToken(",", TokenType.comma, i.current!); 421 | i.moveNext(); 422 | assertToken("5.2", TokenType.literal, i.current!); 423 | i.moveNext(); 424 | assertToken(")", TokenType.closeParen, i.current!); 425 | i.moveNext(); 426 | assertToken("/", TokenType.operator, i.current!); 427 | i.moveNext(); 428 | assertToken("9", TokenType.literal, i.current!); 429 | }); 430 | 431 | test("testTokenizerFunction5", () { 432 | Expression e = new Expression("3/MAX(-3.5,-5.2)/9"); 433 | Iterator i = e.getExpressionTokenizer(); 434 | 435 | i.moveNext(); 436 | assertToken("3", TokenType.literal, i.current!); 437 | i.moveNext(); 438 | assertToken("/", TokenType.operator, i.current!); 439 | i.moveNext(); 440 | assertToken("MAX", TokenType.function, i.current!); 441 | i.moveNext(); 442 | assertToken("(", TokenType.openParen, i.current!); 443 | i.moveNext(); 444 | assertToken("-u", TokenType.unaryOperator, i.current!); 445 | i.moveNext(); 446 | assertToken("3.5", TokenType.literal, i.current!); 447 | i.moveNext(); 448 | assertToken(",", TokenType.comma, i.current!); 449 | i.moveNext(); 450 | assertToken("-u", TokenType.unaryOperator, i.current!); 451 | i.moveNext(); 452 | assertToken("5.2", TokenType.literal, i.current!); 453 | i.moveNext(); 454 | assertToken(")", TokenType.closeParen, i.current!); 455 | i.moveNext(); 456 | assertToken("/", TokenType.operator, i.current!); 457 | i.moveNext(); 458 | assertToken("9", TokenType.literal, i.current!); 459 | }); 460 | 461 | test("testInsertImplicitMultiplication", () { 462 | Expression e = new Expression("22(3+1)"); 463 | Iterator i = e.getExpressionTokenizer(); 464 | 465 | i.moveNext(); 466 | assertToken("22", TokenType.literal, i.current!); 467 | i.moveNext(); 468 | assertToken("(", TokenType.openParen, i.current!); 469 | i.moveNext(); 470 | assertToken("3", TokenType.literal, i.current!); 471 | i.moveNext(); 472 | assertToken("+", TokenType.operator, i.current!); 473 | i.moveNext(); 474 | assertToken("1", TokenType.literal, i.current!); 475 | i.moveNext(); 476 | assertToken(")", TokenType.closeParen, i.current!); 477 | }); 478 | 479 | test("testBracesCustomOperatorAndInIf", () { 480 | Expression e = new Expression("if( (a=0) AND (b=0), 0, 1)"); 481 | 482 | e.addOperator(OperatorImpl("AND", Expression.operatorPrecedenceAnd, false, 483 | booleanOperator: true, fEval: (v1, v2) { 484 | bool b1 = v1.compareTo(Decimal.zero) != 0; 485 | if (!b1) { 486 | return Decimal.zero; 487 | } 488 | bool b2 = v2.compareTo(Decimal.zero) != 0; 489 | return b2 ? Decimal.one : Decimal.zero; 490 | })); 491 | 492 | Decimal? result = (e 493 | ..setStringVariable("a", "0") 494 | ..setStringVariable("b", "0")) 495 | .eval(); 496 | 497 | expect(result.toString(), "0"); 498 | }); 499 | } 500 | -------------------------------------------------------------------------------- /lib/built_ins.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'dart:math' as math; 28 | 29 | import 'package:decimal/decimal.dart'; 30 | import 'package:eval_ex/abstract_lazy_function.dart'; 31 | import 'package:eval_ex/lazy_if_number.dart'; 32 | 33 | import 'abstract_function.dart'; 34 | import 'abstract_operator.dart'; 35 | import 'abstract_unary_operator.dart'; 36 | import 'expression.dart'; 37 | 38 | void addBuiltIns(Expression e) { 39 | e.addOperator(OperatorImpl("+", Expression.operatorPrecedenceAdditive, true, 40 | fEval: (v1, v2) { 41 | return v1 + v2; 42 | })); 43 | 44 | e.addOperator(OperatorImpl("-", Expression.operatorPrecedenceAdditive, true, 45 | fEval: (v1, v2) { 46 | return v1 - v2; 47 | })); 48 | 49 | e.addOperator(OperatorImpl( 50 | "*", Expression.operatorPrecedenceMultiplicative, true, fEval: (v1, v2) { 51 | return v1 * v2; 52 | })); 53 | 54 | e.addOperator(OperatorImpl( 55 | "/", Expression.operatorPrecedenceMultiplicative, true, fEval: (v1, v2) { 56 | if (v2 == Decimal.zero) { 57 | throw new ExpressionException("Cannot divide by 0."); 58 | } 59 | 60 | return (v1 / v2).toDecimal(scaleOnInfinitePrecision: 16); 61 | })); 62 | 63 | e.addOperator(OperatorImpl( 64 | "%", Expression.operatorPrecedenceMultiplicative, true, fEval: (v1, v2) { 65 | return v1 % v2; 66 | })); 67 | 68 | e.addOperator( 69 | OperatorImpl("^", e.powerOperatorPrecedence, false, fEval: (v1, v2) { 70 | return Decimal.parse(math.pow(v1.toDouble(), v2.toDouble()).toString()); 71 | })); 72 | 73 | e.addOperator(OperatorImpl("&&", Expression.operatorPrecedenceAnd, false, 74 | booleanOperator: true, fEval: (v1, v2) { 75 | bool b1 = v1.compareTo(Decimal.zero) != 0; 76 | 77 | if (!b1) { 78 | return Decimal.zero; 79 | } 80 | 81 | bool b2 = v2.compareTo(Decimal.zero) != 0; 82 | 83 | return b2 ? Decimal.one : Decimal.zero; 84 | })); 85 | 86 | e.addOperator(OperatorImpl("||", Expression.operatorPrecedenceOr, false, 87 | booleanOperator: true, fEval: (v1, v2) { 88 | bool b1 = v1.compareTo(Decimal.zero) != 0; 89 | 90 | if (b1) { 91 | return Decimal.one; 92 | } 93 | 94 | bool b2 = v2.compareTo(Decimal.zero) != 0; 95 | 96 | return b2 ? Decimal.one : Decimal.zero; 97 | })); 98 | 99 | e.addOperator(OperatorImpl( 100 | ">", Expression.operatorPrecedenceComparison, false, 101 | booleanOperator: true, fEval: (v1, v2) { 102 | return v1.compareTo(v2) > 0 ? Decimal.one : Decimal.zero; 103 | })); 104 | 105 | e.addOperator(OperatorImpl( 106 | ">=", Expression.operatorPrecedenceComparison, false, 107 | booleanOperator: true, fEval: (v1, v2) { 108 | return v1.compareTo(v2) >= 0 ? Decimal.one : Decimal.zero; 109 | })); 110 | 111 | e.addOperator(OperatorImpl( 112 | "<", Expression.operatorPrecedenceComparison, false, 113 | booleanOperator: true, fEval: (v1, v2) { 114 | return v1.compareTo(v2) < 0 ? Decimal.one : Decimal.zero; 115 | })); 116 | 117 | e.addOperator(OperatorImpl( 118 | "<=", Expression.operatorPrecedenceComparison, false, 119 | booleanOperator: true, fEval: (v1, v2) { 120 | return v1.compareTo(v2) <= 0 ? Decimal.one : Decimal.zero; 121 | })); 122 | 123 | e.addOperator(OperatorNullArgsImpl( 124 | "=", Expression.operatorPrecedenceEquality, false, booleanOperator: true, 125 | fEval: (v1, v2) { 126 | if (v1 == v2) { 127 | return Decimal.one; 128 | } 129 | 130 | if (v1 == null || v2 == null) { 131 | return Decimal.zero; 132 | } 133 | 134 | return v1.compareTo(v2) == 0 ? Decimal.one : Decimal.zero; 135 | })); 136 | 137 | e.addOperator(OperatorNullArgsImpl( 138 | "==", Expression.operatorPrecedenceEquality, false, booleanOperator: true, 139 | fEval: (v1, v2) { 140 | if (v1 == v2) { 141 | return Decimal.one; 142 | } 143 | 144 | if (v1 == null || v2 == null) { 145 | return Decimal.zero; 146 | } 147 | 148 | return v1.compareTo(v2) == 0 ? Decimal.one : Decimal.zero; 149 | })); 150 | 151 | e.addOperator(OperatorNullArgsImpl( 152 | "!=", Expression.operatorPrecedenceEquality, false, booleanOperator: true, 153 | fEval: (v1, v2) { 154 | if (v1 == v2) { 155 | return Decimal.zero; 156 | } 157 | 158 | if (v1 == null || v2 == null) { 159 | return Decimal.one; 160 | } 161 | 162 | return v1.compareTo(v2) != 0 ? Decimal.one : Decimal.zero; 163 | })); 164 | 165 | e.addOperator(OperatorNullArgsImpl( 166 | "<>", Expression.operatorPrecedenceEquality, false, booleanOperator: true, 167 | fEval: (v1, v2) { 168 | if (v1 == v2) { 169 | return Decimal.zero; 170 | } 171 | 172 | if (v1 == null || v2 == null) { 173 | return Decimal.one; 174 | } 175 | 176 | return v1.compareTo(v2) != 0 ? Decimal.one : Decimal.zero; 177 | })); 178 | 179 | e.addLazyFunction(LazyFunctionImpl("STREQ", 2, fEval: (params) { 180 | if (params[0].getString() == params[1].getString()) { 181 | return e.createLazyNumber(Decimal.one); 182 | } else { 183 | return e.createLazyNumber(Decimal.zero); 184 | } 185 | })); 186 | 187 | e.addOperator(UnaryOperatorImpl( 188 | "-", Expression.operatorPrecedenceUnary, false, fEval: (v1) { 189 | return v1 * Decimal.fromInt(-1); 190 | })); 191 | 192 | e.addOperator(UnaryOperatorImpl( 193 | "+", Expression.operatorPrecedenceUnary, false, fEval: (v1) { 194 | return v1 * Decimal.one; 195 | })); 196 | 197 | e.addOperator(OperatorSuffixImpl("!", 61, false, fEval: (v) { 198 | if (v.toDouble() > 50) { 199 | throw new ExpressionException("Operand must be <= 50"); 200 | } 201 | 202 | int number = v.toBigInt().toInt(); 203 | 204 | Decimal factorial = Decimal.one; 205 | for (int i = 1; i <= number; i++) { 206 | factorial = factorial * Decimal.fromInt(i); 207 | } 208 | return factorial; 209 | })); 210 | 211 | e.addFunc( 212 | FunctionImpl("EXP", 2, booleanFunction: false, fEval: (params) { 213 | var v1 = params.first; 214 | var v2 = params.last; 215 | 216 | // Do an more high performance estimate to see if this should be request 217 | // should be canned 218 | double test = math.pow(v1.toDouble(), v2.toDouble()).toDouble(); 219 | if (test.isInfinite) { 220 | throw new ExpressionException("Exponentiation too expensive"); 221 | } else if (test.isNaN) { 222 | throw new ExpressionException("Exponentiation invalid"); 223 | } 224 | 225 | // Thanks to Gene Marin: 226 | // http://stackoverflow.com/questions/3579779/how-to-do-a-fractional-power-on-bigdecimal-in-java 227 | int signOf2 = v2.signum; 228 | double dn1 = v1.toDouble(); 229 | v2 = v2 * Decimal.fromInt(signOf2); 230 | Decimal remainderOf2 = v2.remainder(Decimal.one); 231 | Decimal n2IntPart = v2 - remainderOf2; 232 | Decimal intPow = v1.pow(n2IntPart.toBigInt().toInt()).toDecimal(); 233 | Decimal doublePow = 234 | Decimal.parse(math.pow(dn1, remainderOf2.toDouble()).toString()); 235 | 236 | Decimal result = intPow * doublePow; 237 | if (signOf2 == -1) { 238 | result = (Decimal.one / result).toDecimal(scaleOnInfinitePrecision: 16); 239 | } 240 | 241 | return result; 242 | })); 243 | 244 | e.addFunc(FunctionImpl("FACT", 1, booleanFunction: false, fEval: (params) { 245 | if (params.first.toDouble() > 50) { 246 | throw new ExpressionException("Operand must be <= 50"); 247 | } 248 | 249 | int number = params.first.toBigInt().toInt(); 250 | 251 | Decimal factorial = Decimal.one; 252 | for (int i = 1; i <= number; i++) { 253 | factorial = factorial * Decimal.fromInt(i); 254 | } 255 | return factorial; 256 | })); 257 | 258 | e.addFunc(FunctionImpl("NOT", 1, booleanFunction: true, fEval: (params) { 259 | bool zero = params.first.compareTo(Decimal.zero) == 0; 260 | return zero ? Decimal.one : Decimal.zero; 261 | })); 262 | 263 | e.addLazyFunction(LazyFunctionImpl("IF", 3, fEval: (params) { 264 | return LazyIfNumber(params); 265 | })); 266 | 267 | e.addFunc(FunctionImpl("RANDOM", 0, fEval: (params) { 268 | double d = math.Random().nextDouble(); 269 | return Decimal.parse(d.toString()); 270 | })); 271 | 272 | /* 273 | * Trig functions 274 | */ 275 | // Standard, radians 276 | 277 | e.addFunc(FunctionImpl("SINR", 1, fEval: (params) { 278 | double d = math.sin(params.first.toDouble()); 279 | return Decimal.parse(d.toString()); 280 | })); 281 | 282 | e.addFunc(FunctionImpl("COSR", 1, fEval: (params) { 283 | double d = math.cos(params.first.toDouble()); 284 | return Decimal.parse(d.toString()); 285 | })); 286 | 287 | e.addFunc(FunctionImpl("TANR", 1, fEval: (params) { 288 | double d = math.tan(params.first.toDouble()); 289 | return Decimal.parse(d.toString()); 290 | })); 291 | 292 | e.addFunc(FunctionImpl("COTR", 1, fEval: (params) { 293 | double d = 1.0 / math.tan(params.first.toDouble()); 294 | return Decimal.parse(d.toString()); 295 | })); 296 | 297 | e.addFunc(FunctionImpl("SECR", 1, fEval: (params) { 298 | double d = 1 / math.cos(params.first.toDouble()); 299 | return Decimal.parse(d.toString()); 300 | })); 301 | 302 | e.addFunc(FunctionImpl("CSCR", 1, fEval: (params) { 303 | double d = 1.0 / math.sin(params.first.toDouble()); 304 | return Decimal.parse(d.toString()); 305 | })); 306 | 307 | // Standard, degrees 308 | 309 | e.addFunc(FunctionImpl("SIN", 1, fEval: (params) { 310 | double d = math.sin(degreesToRads(params.first.toDouble())); 311 | return Decimal.parse(d.toString()); 312 | })); 313 | 314 | e.addFunc(FunctionImpl("COS", 1, fEval: (params) { 315 | double d = math.cos(degreesToRads(params.first.toDouble())); 316 | return Decimal.parse(d.toString()); 317 | })); 318 | 319 | e.addFunc(FunctionImpl("TAN", 1, fEval: (params) { 320 | double d = math.tan(degreesToRads(params.first.toDouble())); 321 | return Decimal.parse(d.toString()); 322 | })); 323 | 324 | e.addFunc(FunctionImpl("COT", 1, fEval: (params) { 325 | double d = 1.0 / math.tan(degreesToRads(params.first.toDouble())); 326 | return Decimal.parse(d.toString()); 327 | })); 328 | 329 | e.addFunc(FunctionImpl("SEC", 1, fEval: (params) { 330 | double d = 1 / math.cos(degreesToRads(params.first.toDouble())); 331 | return Decimal.parse(d.toString()); 332 | })); 333 | 334 | e.addFunc(FunctionImpl("CSC", 1, fEval: (params) { 335 | double d = 1.0 / math.sin(degreesToRads(params.first.toDouble())); 336 | return Decimal.parse(d.toString()); 337 | })); 338 | 339 | // Inverse arc functions, radians 340 | 341 | e.addFunc(FunctionImpl("ASINR", 1, fEval: (params) { 342 | double d = math.asin(params.first.toDouble()); 343 | return Decimal.parse(d.toString()); 344 | })); 345 | 346 | e.addFunc(FunctionImpl("ACOSR", 1, fEval: (params) { 347 | double d = math.acos(params.first.toDouble()); 348 | return Decimal.parse(d.toString()); 349 | })); 350 | 351 | e.addFunc(FunctionImpl("ATANR", 1, fEval: (params) { 352 | double d = math.atan(params.first.toDouble()); 353 | return Decimal.parse(d.toString()); 354 | })); 355 | 356 | e.addFunc(FunctionImpl("ACOTR", 1, fEval: (params) { 357 | if (params.first.toDouble() == 0) { 358 | throw new ExpressionException("Number must not be 0"); 359 | } 360 | 361 | double d = math.atan(1.0 / params.first.toDouble()); 362 | return Decimal.parse(d.toString()); 363 | })); 364 | 365 | e.addFunc(FunctionImpl("ATAN2R", 2, fEval: (params) { 366 | double d = math.atan2(params[0].toDouble(), params[1].toDouble()); 367 | return Decimal.parse(d.toString()); 368 | })); 369 | 370 | // Inverse arc functions, degrees 371 | 372 | e.addFunc(FunctionImpl("ASIN", 1, fEval: (params) { 373 | double d = degreesToRads(math.asin(params.first.toDouble())); 374 | return Decimal.parse(d.toString()); 375 | })); 376 | 377 | e.addFunc(FunctionImpl("ACOS", 1, fEval: (params) { 378 | double d = degreesToRads(math.acos(params.first.toDouble())); 379 | return Decimal.parse(d.toString()); 380 | })); 381 | 382 | e.addFunc(FunctionImpl("ATAN", 1, fEval: (params) { 383 | double d = radsToDegrees(math.atan(params.first.toDouble())); 384 | return Decimal.parse(d.toString()); 385 | })); 386 | 387 | e.addFunc(FunctionImpl("ACOT", 1, fEval: (params) { 388 | if (params.first.toDouble() == 0.0) { 389 | throw new ExpressionException("Number must not be 0"); 390 | } 391 | 392 | double d = radsToDegrees(math.atan(1.0 / params.first.toDouble())); 393 | return Decimal.parse(d.toString()); 394 | })); 395 | 396 | e.addFunc(FunctionImpl("ATAN2", 2, fEval: (params) { 397 | double d = 398 | radsToDegrees(math.atan2(params[0].toDouble(), params[1].toDouble())); 399 | return Decimal.parse(d.toString()); 400 | })); 401 | 402 | // Conversions 403 | e.addFunc(FunctionImpl("RAD", 1, fEval: (params) { 404 | double d = degreesToRads(params.first.toDouble()); 405 | return Decimal.parse(d.toString()); 406 | })); 407 | 408 | e.addFunc(FunctionImpl("DEG", 1, fEval: (params) { 409 | double d = radsToDegrees(params.first.toDouble()); 410 | return Decimal.parse(d.toString()); 411 | })); 412 | 413 | e.addFunc(FunctionImpl("MAX", -1, fEval: (params) { 414 | if (params.isEmpty) { 415 | throw new ExpressionException("MAX requires at least one parameter"); 416 | } 417 | Decimal? max; 418 | for (Decimal param in params) { 419 | if (max == null || param.compareTo(max) > 0) { 420 | max = param; 421 | } 422 | } 423 | 424 | return max; 425 | })); 426 | 427 | e.addFunc(FunctionImpl("MIN", -1, fEval: (params) { 428 | if (params.isEmpty) { 429 | throw new ExpressionException("MIN requires at least one parameter"); 430 | } 431 | Decimal? min; 432 | for (Decimal param in params) { 433 | if (min == null || param.compareTo(min) < 0) { 434 | min = param; 435 | } 436 | } 437 | 438 | return min; 439 | })); 440 | 441 | e.addFunc(FunctionImpl("ABS", 1, fEval: (params) { 442 | return params.first.abs(); 443 | })); 444 | 445 | e.addFunc(FunctionImpl("LOG", 1, fEval: (params) { 446 | double d = math.log(params.first.toDouble()); 447 | return Decimal.parse(d.toString()); 448 | })); 449 | 450 | e.addFunc(FunctionImpl("LOG10", 1, fEval: (params) { 451 | double d = log10(params.first.toDouble()); 452 | return Decimal.parse(d.toString()); 453 | })); 454 | 455 | e.addFunc(FunctionImpl("ROUND", 2, fEval: (params) { 456 | Decimal toRound = params.first; 457 | return Decimal.parse(toRound.toStringAsFixed(params[1].toBigInt().toInt())); 458 | })); 459 | 460 | e.addFunc(FunctionImpl("FLOOR", 1, fEval: (params) { 461 | return params.first.floor(); 462 | })); 463 | 464 | e.addFunc(FunctionImpl("CEILING", 1, fEval: (params) { 465 | return params.first.ceil(); 466 | })); 467 | 468 | e.addFunc(FunctionImpl("SQRT", 1, fEval: (params) { 469 | return Decimal.parse(math.sqrt(params.first.toDouble()).toString()); 470 | })); 471 | 472 | e.addFunc(FunctionImpl("CUBEROOT", 1, fEval: (params) { 473 | final double n = params.first.toDouble(); 474 | if (n < 0) { 475 | throw const ExpressionException('Number must not be smaller than 0.'); 476 | } 477 | return Decimal.parse((math.pow(n, 1 / 3)).toString()); 478 | })); 479 | 480 | e.variables["theAnswerToLifeTheUniverseAndEverything"] = 481 | e.createLazyNumber(Decimal.fromInt(42)); 482 | 483 | e.variables["e"] = e.createLazyNumber(Expression.e); 484 | e.variables["PI"] = e.createLazyNumber(Expression.pi); 485 | e.variables["NULL"] = null; 486 | e.variables["null"] = null; 487 | e.variables["Null"] = null; 488 | e.variables["TRUE"] = e.createLazyNumber(Decimal.one); 489 | e.variables["true"] = e.createLazyNumber(Decimal.one); 490 | e.variables["True"] = e.createLazyNumber(Decimal.one); 491 | e.variables["FALSE"] = e.createLazyNumber(Decimal.zero); 492 | e.variables["false"] = e.createLazyNumber(Decimal.zero); 493 | e.variables["False"] = e.createLazyNumber(Decimal.zero); 494 | } 495 | 496 | // Expects two, non null arguments 497 | class OperatorImpl extends AbstractOperator { 498 | Function(Decimal v1, Decimal v2) fEval; 499 | 500 | OperatorImpl(String oper, int precedence, bool leftAssoc, 501 | {bool booleanOperator = false, 502 | bool unaryOperator = false, 503 | required this.fEval}) 504 | : super(oper, precedence, leftAssoc, 505 | booleanOperator: booleanOperator, unaryOperator: unaryOperator); 506 | 507 | @override 508 | Decimal eval(Decimal? v1, Decimal? v2) { 509 | if (v1 == null) { 510 | throw new AssertionError("First operand may not be null."); 511 | } 512 | if (v2 == null) { 513 | throw new AssertionError("Second operand may not be null."); 514 | } 515 | 516 | return fEval(v1, v2); 517 | } 518 | } 519 | 520 | class OperatorSuffixImpl extends AbstractOperator { 521 | Function(Decimal v1) fEval; 522 | 523 | OperatorSuffixImpl(String oper, int precedence, bool leftAssoc, 524 | {bool booleanOperator = false, required this.fEval}) 525 | : super(oper, precedence, leftAssoc, 526 | booleanOperator: booleanOperator, unaryOperator: true); 527 | 528 | @override 529 | Decimal eval(Decimal? v1, Decimal? v2) { 530 | if (v1 == null) { 531 | throw new AssertionError("First operand may not be null."); 532 | } 533 | if (v2 != null) { 534 | throw new AssertionError("Did not expect second operand."); 535 | } 536 | 537 | return fEval(v1); 538 | } 539 | } 540 | 541 | class OperatorNullArgsImpl extends AbstractOperator { 542 | Function(Decimal? v1, Decimal? v2) fEval; 543 | 544 | OperatorNullArgsImpl(String oper, int precedence, bool leftAssoc, 545 | {bool booleanOperator = false, 546 | bool unaryOperator = false, 547 | required this.fEval}) 548 | : super(oper, precedence, leftAssoc, 549 | booleanOperator: booleanOperator, unaryOperator: unaryOperator); 550 | 551 | @override 552 | Decimal eval(Decimal? v1, Decimal? v2) { 553 | return fEval(v1, v2); 554 | } 555 | } 556 | 557 | class UnaryOperatorImpl extends AbstractUnaryOperator { 558 | // Type defs? 559 | Function(Decimal v1) fEval; 560 | 561 | UnaryOperatorImpl(String oper, int precedence, bool leftAssoc, 562 | {required this.fEval}) 563 | : super(oper, precedence, leftAssoc); 564 | 565 | @override 566 | Decimal evalUnary(Decimal? v1) { 567 | if (v1 == null) { 568 | throw new AssertionError("Operand may not be null."); 569 | } 570 | 571 | return fEval(v1); 572 | } 573 | } 574 | 575 | class FunctionImpl extends AbstractFunction { 576 | Function(List) fEval; 577 | 578 | FunctionImpl(String name, int numParams, 579 | {bool booleanFunction = false, required this.fEval}) 580 | : super(name, numParams, booleanFunction: booleanFunction); 581 | 582 | @override 583 | Decimal eval(List parameters) { 584 | List params = []; 585 | 586 | for (int i = 0; i < parameters.length; i++) { 587 | if (parameters[i] == null) { 588 | throw new AssertionError("Operand #${i + 1} may not be null."); 589 | } 590 | 591 | params.add(parameters[i]!); 592 | } 593 | 594 | return fEval(params); 595 | } 596 | } 597 | 598 | class LazyFunctionImpl extends AbstractLazyFunction { 599 | Function(List) fEval; 600 | 601 | LazyFunctionImpl(String name, int numParams, 602 | {bool booleanFunction = false, required this.fEval}) 603 | : super(name, numParams, booleanFunction: booleanFunction); 604 | 605 | @override 606 | LazyNumber lazyEval(List lazyParams) { 607 | List params = []; 608 | 609 | for (int i = 0; i < lazyParams.length; i++) { 610 | if (lazyParams[i] == null) { 611 | throw new AssertionError("Operand #${i + 1} may not be null."); 612 | } 613 | 614 | params.add(lazyParams[i]!); 615 | } 616 | 617 | return fEval(params); 618 | } 619 | } 620 | 621 | double log10(num x) => math.log(x) / math.ln10; 622 | 623 | double degreesToRads(double deg) { 624 | return deg * (math.pi / 180); 625 | } 626 | 627 | double radsToDegrees(double rad) { 628 | return rad * (180 / math.pi); 629 | } 630 | -------------------------------------------------------------------------------- /test/eval_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'package:decimal/decimal.dart'; 28 | import 'package:eval_ex/expression.dart'; 29 | import 'package:test/test.dart'; 30 | 31 | void main() { 32 | test("testsInAB", () { 33 | String err = ""; 34 | try { 35 | Expression expression = Expression("sin(a+x)"); 36 | expression.eval(); 37 | } on ExpressionException catch (e) { 38 | err = e.msg; 39 | } 40 | 41 | expect(err, "Unknown operator or function: a"); 42 | }); 43 | 44 | test("testInvalidExpressions1", () { 45 | String err = ""; 46 | try { 47 | Expression expression = Expression("12 18 2"); 48 | expression.eval(); 49 | } on ExpressionException catch (e) { 50 | err = e.msg; 51 | } 52 | 53 | expect(err, "Missing operator at character position 3"); 54 | }); 55 | 56 | test("testInvalidExpressions3", () { 57 | String err = ""; 58 | try { 59 | Expression expression = Expression("12 + * 18"); 60 | expression.eval(); 61 | } on ExpressionException catch (e) { 62 | err = e.msg; 63 | } 64 | 65 | expect(err, "Unknown unary operator * at character position 6"); 66 | }); 67 | 68 | test("testInvalidExpressions4", () { 69 | String err = ""; 70 | try { 71 | Expression expression = Expression(""); 72 | expression.eval(); 73 | } on ExpressionException catch (e) { 74 | err = e.msg; 75 | } 76 | 77 | expect(err, "Empty expression"); 78 | }); 79 | 80 | test("testInvalidExpressions5", () { 81 | String err = ""; 82 | try { 83 | Expression expression = Expression("1 1+2/"); 84 | expression.eval(); 85 | } on ExpressionException catch (e) { 86 | err = e.msg; 87 | } 88 | 89 | expect(err, "Missing operator at character position 2"); 90 | }); 91 | 92 | test("testBrackets", () { 93 | expect(new Expression("(1+2)").eval().toString(), "3"); 94 | expect(new Expression("((1+2))").eval().toString(), "3"); 95 | expect(new Expression("(((1+2)))").eval().toString(), "3"); 96 | expect(new Expression("(1+2)*(1+2)").eval().toString(), "9"); 97 | expect(new Expression("(1+2)*(1+2)+1").eval().toString(), "10"); 98 | expect(new Expression("(1+2)*((1+2)+1)").eval().toString(), "12"); 99 | }); 100 | 101 | test("testUnknown1", () { 102 | expect(() => Expression("7#9").eval().toString(), 103 | throwsA(isA())); 104 | }); 105 | 106 | test("testUnknown2", () { 107 | expect(() => Expression("123.6*-9.8-7#9").eval().toString(), 108 | throwsA(isA())); 109 | }); 110 | 111 | test("testSimple", () { 112 | expect(new Expression("1+2").eval().toString(), "3"); 113 | expect(new Expression("4/2").eval().toString(), "2"); 114 | expect(new Expression("3+4/2").eval().toString(), "5"); 115 | expect(new Expression("(3+4)/2").eval().toString(), "3.5"); 116 | expect(new Expression("4.2*1.9").eval().toString(), "7.98"); 117 | expect(new Expression("8%3").eval().toString(), "2"); 118 | expect(new Expression("8%2").eval().toString(), "0"); 119 | expect(new Expression("2*.1").eval().toString(), "0.2"); 120 | }); 121 | 122 | test("testUnaryMinus", () { 123 | expect(Expression("-3").eval().toString(), "-3"); 124 | expect(Expression("-SQRT(4)").eval().toString(), "-2"); 125 | expect(Expression("-(5*3+(+10-13))").eval().toString(), "-12"); 126 | expect(Expression("-2+3/4*-1").eval().toString(), "-2.75"); 127 | expect(Expression("-3^2").eval().toString(), "9"); 128 | expect(Expression("4^-0.5").eval().toString(), "0.5"); 129 | expect(Expression("-2+3/4").eval().toString(), "-1.25"); 130 | expect(Expression("-(3+-4*-1/-2)").eval().toString(), "-1"); 131 | expect(Expression("2+-.2").eval().toString(), "1.8"); 132 | }); 133 | 134 | test("testUnaryPlus", () { 135 | expect(Expression("+3").eval().toString(), "3"); 136 | expect(Expression("+(3-1+2)").eval().toString(), "4"); 137 | expect(Expression("+(3-(+1)+2)").eval().toString(), "4"); 138 | expect(Expression("+3^2").eval().toString(), "9"); 139 | }); 140 | 141 | test("testPow", () { 142 | expect(Expression("2^4").eval().toString(), "16"); 143 | expect(Expression("2^8").eval().toString(), "256"); 144 | expect(Expression("3^2").eval().toString(), "9"); 145 | expect(Expression("2.5^2").eval().toString(), "6.25"); 146 | expect(Expression("2.6^3.5").eval().toString(), "28.340448436819067"); 147 | expect(Expression("EXP(PI, 2)").eval()!.toStringAsPrecision(128), 148 | "9.8696044010893586188344909998761511353136994072407906264133493762200448224192052430017734037185522313078742635808502091666098354"); 149 | 150 | expect( 151 | () => Expression("9^9^9").eval(), throwsA(isA())); 152 | }); 153 | 154 | test("testSqrt", () { 155 | expect(Expression("SQRT(16)").eval().toString(), "4"); 156 | expect(Expression("SQRT(2)").eval().toString(), "1.4142135623730951"); 157 | // TODO - due to a bad SQRT implementation 158 | //expect(Expression("SQRT(2)").eval().toStringAsPrecision(128), "1.41421356237309504880168872420969807856967187537694807317667973799073247846210703885038753432764157273501384623091229702492483605"); 159 | expect(Expression("SQRT(5)").eval().toString(), "2.23606797749979"); 160 | expect(Expression("SQRT(9875)").eval().toString(), "99.37303457175895"); 161 | expect(Expression("SQRT(5.55)").eval().toString(), "2.355843797877949"); 162 | expect(Expression("SQRT(0)").eval().toString(), "0"); 163 | }); 164 | 165 | test("testFunctions", () { 166 | expect(Expression("Random()").eval().toString(), isNot(equals("1.5"))); 167 | expect(Expression("SIN(23.6)").eval().toString(), "0.40034903255689497"); 168 | expect(Expression("MAX(-7,8)").eval().toString(), "8"); 169 | expect(Expression("MAX(3,max(4,5))").eval().toString(), "5"); 170 | expect(Expression("MAX(3,max(MAX(9.6,-4.2),Min(5,9)))").eval().toString(), 171 | "9.6"); 172 | expect(Expression("LOG(10)").eval().toString(), "2.302585092994046"); 173 | }); 174 | 175 | test("testExpectedParameterNumbers", () { 176 | String err = ""; 177 | try { 178 | Expression expression = Expression("Random(1)"); 179 | expression.eval(); 180 | } on ExpressionException catch (e) { 181 | err = e.msg; 182 | } 183 | 184 | expect(err, "Function Random expected 0 parameters, got 1"); 185 | 186 | try { 187 | Expression expression = Expression("SIN(1, 6)"); 188 | expression.eval(); 189 | } on ExpressionException catch (e) { 190 | err = e.msg; 191 | } 192 | 193 | expect(err, "Function SIN expected 1 parameters, got 2"); 194 | }); 195 | 196 | test("testVariableParameterNumbers", () { 197 | String err = ""; 198 | try { 199 | Expression expression = Expression("min()"); 200 | expression.eval(); 201 | } on ExpressionException catch (e) { 202 | err = e.msg; 203 | } 204 | 205 | expect(err, "MIN requires at least one parameter"); 206 | 207 | expect(Expression("min(1)").eval().toString(), "1"); 208 | expect(Expression("min(1, 2)").eval().toString(), "1"); 209 | expect(Expression("min(1, 2, 3)").eval().toString(), "1"); 210 | expect(Expression("max(3, 2, 1)").eval().toString(), "3"); 211 | expect( 212 | Expression("max(3, 2, 1, 4, 5, 6, 7, 8, 9, 0)").eval().toString(), "9"); 213 | }); 214 | 215 | test("testOrphanedOperators", () { 216 | String err = ""; 217 | try { 218 | Expression expression = Expression("/"); 219 | expression.eval(); 220 | } on ExpressionException catch (e) { 221 | err = e.msg; 222 | } 223 | 224 | expect(err, "Unknown unary operator / at character position 1"); 225 | 226 | try { 227 | Expression expression = Expression("3/"); 228 | expression.eval(); 229 | } on ExpressionException catch (e) { 230 | err = e.msg; 231 | } 232 | 233 | expect(err, "Missing parameter(s) for operator /"); 234 | 235 | try { 236 | Expression expression = Expression("/3"); 237 | expression.eval(); 238 | } on ExpressionException catch (e) { 239 | err = e.msg; 240 | } 241 | 242 | expect(err, "Unknown unary operator / at character position 1"); 243 | 244 | try { 245 | Expression expression = Expression("SIN(MAX(23,45,12))/"); 246 | expression.eval(); 247 | } on ExpressionException catch (e) { 248 | err = e.msg; 249 | } 250 | 251 | expect(err, "Missing parameter(s) for operator /"); 252 | }); 253 | 254 | test("closeParenAtStartCausesExpressionException", () { 255 | expect(() => Expression("(").eval(), throwsA(isA())); 256 | }); 257 | 258 | test("testOrphanedOperatorsInFunctionParameters", () { 259 | String err = ""; 260 | try { 261 | Expression expression = Expression("min(/)"); 262 | expression.eval(); 263 | } on ExpressionException catch (e) { 264 | err = e.msg; 265 | } 266 | 267 | expect(err, "Unknown unary operator / at character position 5"); 268 | 269 | try { 270 | Expression expression = Expression("min(3/)"); 271 | expression.eval(); 272 | } on ExpressionException catch (e) { 273 | err = e.msg; 274 | } 275 | 276 | expect(err, "Missing parameter(s) for operator / at character position 5"); 277 | 278 | try { 279 | Expression expression = Expression("min(/3)"); 280 | expression.eval(); 281 | } on ExpressionException catch (e) { 282 | err = e.msg; 283 | } 284 | 285 | expect(err, "Unknown unary operator / at character position 5"); 286 | 287 | try { 288 | Expression expression = Expression("SIN(MAX(23,45,12,23.6/))"); 289 | expression.eval(); 290 | } on ExpressionException catch (e) { 291 | err = e.msg; 292 | } 293 | 294 | expect(err, "Missing parameter(s) for operator / at character position 21"); 295 | 296 | try { 297 | Expression expression = Expression("SIN(MAX(23,45,12/,23.6))"); 298 | expression.eval(); 299 | } on ExpressionException catch (e) { 300 | err = e.msg; 301 | } 302 | 303 | expect(err, "Missing parameter(s) for operator / at character position 16"); 304 | 305 | try { 306 | Expression expression = Expression("SIN(MAX(23,45,>=12,23.6))"); 307 | expression.eval(); 308 | } on ExpressionException catch (e) { 309 | err = e.msg; 310 | } 311 | 312 | expect(err, "Unknown unary operator >= at character position 15"); 313 | 314 | try { 315 | Expression expression = Expression("SIN(MAX(>=23,45,12,23.6))"); 316 | expression.eval(); 317 | } on ExpressionException catch (e) { 318 | err = e.msg; 319 | } 320 | 321 | expect(err, "Unknown unary operator >= at character position 9"); 322 | }); 323 | 324 | test("testExtremeFunctionNesting", () { 325 | expect(Expression("Random()").eval().toString(), isNot(equals("1.5"))); 326 | expect(Expression("SIN(SIN(COS(23.6)))").eval().toString(), 327 | "0.0002791281464253652"); 328 | expect( 329 | Expression("MIN(0, SIN(SIN(COS(23.6))), 0-MAX(3,4,MAX(0,SIN(1))), 10)") 330 | .eval() 331 | .toString(), 332 | "-4"); 333 | }); 334 | 335 | test("testTrigonometry", () { 336 | expect(Expression("SIN(30)").eval().toString(), "0.49999999999999994"); 337 | expect(Expression("cos(30)").eval().toString(), "0.8660254037844387"); 338 | expect(Expression("TAN(30)").eval().toString(), "0.5773502691896257"); 339 | expect(Expression("RAD(30)").eval().toString(), "0.5235987755982988"); 340 | expect(Expression("DEG(30)").eval().toString(), "1718.8733853924696"); 341 | expect( 342 | Expression("atan(0.5773503)").eval().toString(), "30.000001323978292"); 343 | expect(Expression("atan2(0.5773503, 1)").eval().toString(), 344 | "30.000001323978292"); 345 | expect(Expression("atan2(2, 3)").eval().toString(), "33.690067525979785"); 346 | expect(Expression("atan2(2, -3)").eval().toString(), "146.30993247402023"); 347 | expect( 348 | Expression("atan2(-2, -3)").eval().toString(), "-146.30993247402023"); 349 | expect(Expression("atan2(-2, 3)").eval().toString(), "-33.690067525979785"); 350 | expect(Expression("SEC(30)").eval().toString(), "1.1547005383792515"); 351 | expect(Expression("SEC(45)").eval().toString(), "1.414213562373095"); 352 | expect(Expression("SEC(60)").eval().toString(), "1.9999999999999996"); 353 | expect(Expression("SEC(75)").eval().toString(), "3.8637033051562737"); 354 | expect(Expression("CSC(30)").eval().toString(), "2.0000000000000004"); 355 | expect(Expression("CSC(45)").eval().toString(), "1.414213562373095"); 356 | expect(Expression("CSC(60)").eval().toString(), "1.1547005383792517"); 357 | expect(Expression("CSC(75)").eval().toString(), "1.035276180410083"); 358 | expect(Expression("COT(30)").eval().toString(), "1.7320508075688774"); 359 | expect(Expression("COT(45)").eval().toString(), "1.0000000000000002"); 360 | expect(Expression("COT(60)").eval().toString(), "0.577350269189626"); 361 | expect(Expression("COT(75)").eval().toString(), "0.2679491924311227"); 362 | expect(Expression("ACOT(30)").eval().toString(), "1.9091524329963763"); 363 | expect(Expression("ACOT(45)").eval().toString(), "1.2730300200567113"); 364 | expect(Expression("ACOT(60)").eval().toString(), "0.9548412538721887"); 365 | expect(Expression("ACOT(75)").eval().toString(), "0.7638984609299951"); 366 | // TODO hyperbolic not implemented yet 367 | // expect(Expression("SINH(30)").eval().toString(), "5343237000000"); 368 | // expect(Expression("COSH(30)").eval().toString(), "5343237000000"); 369 | // expect(Expression("TANH(30)").eval().toString(), "1"); 370 | // expect(Expression("SECH(30)").eval().toString(), "0.0000000000001871525"); 371 | // expect(Expression("SECH(45)").eval().toString(), "0.00000000000000000005725037"); 372 | // expect(Expression("SECH(60)").eval().toString(), "0.00000000000000000000000001751302"); 373 | // expect(Expression("SECH(75)").eval().toString(), "0.000000000000000000000000000000005357274"); 374 | // expect(Expression("CSCH(30)").eval().toString(), "0.0000000000001871525"); 375 | // expect(Expression("CSCH(45)").eval().toString(), "0.00000000000000000005725037"); 376 | // expect(Expression("CSCH(60)").eval().toString(), "0.00000000000000000000000001751302"); 377 | // expect(Expression("CSCH(75)").eval().toString(), "0.000000000000000000000000000000005357274"); 378 | // expect(Expression("COTH(30)").eval().toString(), "1"); 379 | // expect(Expression("COTH(1.2)").eval().toString(), "1.199538"); 380 | // expect(Expression("COTH(2.4)").eval().toString(), "1.016596"); 381 | // expect(Expression("ASINH(30)").eval().toString(), "4.094622"); 382 | // expect(Expression("ASINH(45)").eval().toString(), "4.499933"); 383 | // expect(Expression("ASINH(60)").eval().toString(), "4.787561"); 384 | // expect(Expression("ASINH(75)").eval().toString(), "5.01068"); 385 | // expect(Expression("ACOSH(1)").eval().toString(), "0"); 386 | // expect(Expression("ACOSH(30)").eval().toString(), "4.094067"); 387 | // expect(Expression("ACOSH(45)").eval().toString(), "4.499686"); 388 | // expect(Expression("ACOSH(60)").eval().toString(), "4.787422"); 389 | // expect(Expression("ACOSH(75)").eval().toString(), "5.010591"); 390 | // expect(Expression("ATANH(0)").eval().toString(), "0"); 391 | // expect(Expression("ATANH(0.5)").eval().toString(), "0.5493061"); 392 | // expect(Expression("ATANH(-0.5)").eval().toString(), "-0.5493061"); 393 | }); 394 | 395 | test("testMinMaxAbs", () { 396 | expect(Expression("MAX(3.78787,3.78786)").eval().toString(), "3.78787"); 397 | expect(Expression("max(3.78786,3.78787)").eval().toString(), "3.78787"); 398 | expect(Expression("MIN(3.78787,3.78786)").eval().toString(), "3.78786"); 399 | expect(Expression("Min(3.78786,3.78787)").eval().toString(), "3.78786"); 400 | expect(Expression("aBs(-2.123)").eval().toString(), "2.123"); 401 | expect(Expression("abs(2.123)").eval().toString(), "2.123"); 402 | }); 403 | 404 | test("testRounding", () { 405 | expect(Expression("round(3.78787,1)").eval().toString(), "3.8"); 406 | expect(Expression("round(3.78787,3)").eval().toString(), "3.788"); 407 | expect(Expression("round(3.7345,3)").eval().toString(), "3.735"); 408 | expect(Expression("round(-3.7345,3)").eval().toString(), "-3.735"); 409 | expect(Expression("round(-3.78787,2)").eval().toString(), "-3.79"); 410 | expect(Expression("round(123.78787,2)").eval().toString(), "123.79"); 411 | expect(Expression("floor(3.78787)").eval().toString(), "3"); 412 | expect(Expression("ceiling(3.78787)").eval().toString(), "4"); 413 | expect(Expression("floor(-2.1)").eval().toString(), "-3"); 414 | expect(Expression("ceiling(-2.1)").eval().toString(), "-2"); 415 | }); 416 | 417 | test("testMathContext", () { 418 | Expression e; 419 | e = new Expression("2.5/3"); 420 | expect(e.eval()!.toStringAsPrecision(2), "0.83"); 421 | 422 | e = new Expression("2.5/3"); 423 | expect(e.eval()!.toStringAsPrecision(3), "0.833"); 424 | 425 | e = new Expression("2.5/3"); 426 | expect(e.eval()!.toStringAsPrecision(8), "0.83333333"); 427 | }); 428 | 429 | test("unknownFunctionsFailGracefully", () { 430 | String err = ""; 431 | try { 432 | new Expression("unk(1,2,3)").eval(); 433 | } on ExpressionException catch (e) { 434 | err = e.msg; 435 | } 436 | 437 | expect(err, "Unknown function unk at character position 1"); 438 | }); 439 | 440 | test("unknownOperatorsFailGracefully", () { 441 | String err = ""; 442 | try { 443 | new Expression("a |*| b").eval(); 444 | } on ExpressionException catch (e) { 445 | err = e.msg; 446 | } 447 | 448 | expect(err, "Unknown operator |*| at character position 3"); 449 | }); 450 | 451 | test("testNull", () { 452 | expect(Expression("null").eval(), null); 453 | }); 454 | 455 | test("testCalculationWithNull", () { 456 | String err = ""; 457 | try { 458 | new Expression("null+1").eval(); 459 | } on AssertionError catch (e) { 460 | err = e.message.toString(); 461 | } 462 | expect(err, "First operand may not be null."); 463 | 464 | err = ""; 465 | try { 466 | new Expression("1 + NULL").eval(); 467 | } on AssertionError catch (e) { 468 | err = e.message.toString(); 469 | } 470 | expect(err, "Second operand may not be null."); 471 | 472 | err = ""; 473 | try { 474 | new Expression("round(Null, 1)").eval(); 475 | } on AssertionError catch (e) { 476 | err = e.message.toString(); 477 | } 478 | expect(err, "Operand #1 may not be null."); 479 | 480 | err = ""; 481 | try { 482 | new Expression("round(1, Null)").eval(); 483 | } on AssertionError catch (e) { 484 | err = e.message.toString(); 485 | } 486 | expect(err, "Operand #2 may not be null."); 487 | }); 488 | 489 | test("canEvalHexExpression", () { 490 | Decimal result = new Expression("0xcafe").eval()!; 491 | expect(result.toString(), "51966"); 492 | }); 493 | 494 | test("hexExpressionCanUseUpperCaseCharacters", () { 495 | Decimal result = new Expression("0XCAFE").eval()!; 496 | expect(result.toString(), "51966"); 497 | }); 498 | 499 | test("hexMinus", () { 500 | Decimal result = new Expression("-0XCAFE").eval()!; 501 | expect(result.toString(), "-51966"); 502 | }); 503 | 504 | test("hexMinusBlanks", () { 505 | Decimal result = new Expression(" 0xa + -0XCAFE ").eval()!; 506 | expect(result.toString(), "-51956"); 507 | }); 508 | 509 | test("longHexExpressionWorks", () { 510 | Decimal result = new Expression("0xcafebabe").eval()!; 511 | expect(result.toString(), "3405691582"); 512 | }); 513 | 514 | test("hexExpressionDoesNotAllowNonHexCharacters", () { 515 | expect( 516 | () => Expression("0xbaby").eval(), throwsA(isA())); 517 | }); 518 | 519 | test("throwsExceptionIfDoesNotContainHexDigits", () { 520 | expect(() => Expression("0x").eval(), throwsA(isA())); 521 | }); 522 | 523 | test("hexExpressionsEvaluatedAsExpected", () { 524 | Decimal result = new Expression("0xcafe + 0xbabe").eval()!; 525 | expect(result.toString(), "99772"); 526 | }); 527 | 528 | test("testResultZeroStripping", () { 529 | Expression expression = new Expression("200.40000 / 2"); 530 | expect(expression.eval().toString(), "100.2"); 531 | }); 532 | 533 | test("testImplicitMultiplication", () { 534 | Expression expression = new Expression("22(3+1)"); 535 | expect(expression.eval().toString(), "88"); 536 | 537 | expression = new Expression("(a+b)(a-b)") 538 | ..setStringVariable("a", "1") 539 | ..setStringVariable("b", "2"); 540 | 541 | expect(expression.eval().toString(), "-3"); 542 | 543 | expression = new Expression("0xA(a+b)") 544 | ..setStringVariable("a", "1") 545 | ..setStringVariable("b", "2"); 546 | 547 | expect(expression.eval().toString(), "30"); 548 | }); 549 | 550 | test("testNoLeadingZeros", () { 551 | Expression e = new Expression("0.1 + .1"); 552 | expect(e.eval().toString(), "0.2"); 553 | 554 | e = new Expression(".2*.3"); 555 | expect(e.eval().toString(), "0.06"); 556 | 557 | e = new Expression(".2*.3+.1"); 558 | expect(e.eval().toString(), "0.16"); 559 | }); 560 | 561 | test("testUnexpectedComma", () { 562 | String err = ""; 563 | try { 564 | Expression expression = new Expression("2+3,8"); 565 | expression.eval(); 566 | } on ExpressionException catch (e) { 567 | err = e.msg; 568 | } 569 | expect(err, "Unexpected comma at character position 3"); 570 | }); 571 | 572 | test("testStrEq", () { 573 | Expression e = 574 | new Expression("STREQ(\"test32asd234@#\",\"test32asd234@#\")"); 575 | expect(e.eval().toString(), "1"); 576 | 577 | e = new Expression("STREQ(\"test\",\"test32asd234@#\")"); 578 | expect(e.eval().toString(), "0"); 579 | 580 | e = new Expression("STREQ(\"\",\"\")"); 581 | expect(e.eval().toString(), "1"); 582 | }); 583 | 584 | test("testMultipleCases", () { 585 | Expression expression = new Expression("L * l * h"); 586 | 587 | expression.setStringVariable("L", "30"); 588 | expression.setStringVariable("l", "40"); 589 | expression.setStringVariable("h", "50"); 590 | 591 | expect(expression.variables['L']!.getString(), "30"); 592 | expect(expression.variables['l']!.getString(), "40"); 593 | expect(expression.variables['h']!.getString(), "50"); 594 | }); 595 | 596 | test("testFast", () { 597 | Decimal? expression = Expression("1.000000000000001^1234567").eval(); 598 | 599 | expect(expression.toString(), "1.0000000013706447"); 600 | }); 601 | } 602 | -------------------------------------------------------------------------------- /lib/expression.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 Udo Klimaschewski 3 | * 4 | * http://about.me/udo.klimaschewski 5 | * http://UdoJava.com/ 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 | import 'dart:collection'; 28 | import 'dart:core'; 29 | 30 | import 'package:decimal/decimal.dart'; 31 | import 'package:eval_ex/abstract_unary_operator.dart'; 32 | import 'package:eval_ex/built_ins.dart'; 33 | import 'package:eval_ex/func.dart'; 34 | import 'package:eval_ex/lazy_function.dart'; 35 | import 'package:eval_ex/lazy_operator.dart'; 36 | import 'package:eval_ex/utils.dart'; 37 | 38 | class Expression { 39 | /// Unary operators precedence: + and - as prefix 40 | static const int operatorPrecedenceUnary = 60; 41 | 42 | /// Equality operators precedence: =, ==, !=. <> 43 | static const int operatorPrecedenceEquality = 7; 44 | 45 | /// Comparative operators precedence: <,>,<=,>= 46 | static const int operatorPrecedenceComparison = 10; 47 | 48 | /// Or operator precedence: || 49 | static const int operatorPrecedenceOr = 2; 50 | 51 | /// And operator precedence: && 52 | static const int operatorPrecedenceAnd = 4; 53 | 54 | /// Power operator precedence: ^ 55 | static const int operatorPrecedencePower = 40; 56 | 57 | /// An optional higher power operator precedence. {@link ExpressionSettings} 58 | static const int operatorPrecedencePowerHigher = 80; 59 | 60 | /// Multiplicative operators precedence: *,/,% 61 | static const int operatorPrecedenceMultiplicative = 30; 62 | 63 | /// Additive operators precedence: + and - 64 | static const int operatorPrecedenceAdditive = 20; 65 | 66 | /// Definition of PI as a constant, can be used in expressions as variable. 67 | static final Decimal pi = Decimal.parse( 68 | "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679"); 69 | 70 | /// Definition of e: "Euler's number" as a constant, can be used in expressions as variable. 71 | static final Decimal e = Decimal.parse( 72 | "2.71828182845904523536028747135266249775724709369995957496696762772407663"); 73 | 74 | /// Exception message for missing operators. 75 | static final String missingParametersForOperator = 76 | "Missing parameter(s) for operator "; 77 | 78 | /// The precedence of the power (^) operator. Default is 40. 79 | int powerOperatorPrecedence = operatorPrecedencePower; 80 | 81 | /// The characters (other than letters and digits) allowed as the first character in a variable. 82 | String _firstVarChars = "_"; 83 | 84 | /// The characters (other than letters and digits) allowed as the second or subsequent characters in a variable. 85 | String _varChars = "_"; 86 | 87 | /// The current infix expression, with optional variable substitutions. 88 | String _expressionString; 89 | 90 | /// The cached RPN (Reverse Polish Notation) of the expression. 91 | List? _rpn; 92 | 93 | /// All defined operators with name and implementation. 94 | Map operators = SplayTreeMap((a, b) => a.compareTo(b)); 95 | 96 | /// All defined functions with name and implementation. 97 | Map functions = SplayTreeMap((a, b) => a.compareTo(b)); 98 | 99 | /// All defined variables with name and value. 100 | Map variables = SplayTreeMap((a, b) => a.compareTo(b)); 101 | 102 | /// What character to use for decimal separators. 103 | static const String _decimalSeparator = "."; 104 | 105 | /// What character to use for minus sign (negative values). 106 | static const String _minusSign = "-"; 107 | 108 | /// The BigDecimal representation of the left parenthesis, used for parsing varying numbers of function parameters. 109 | static final LazyNumber _paramsStart = 110 | LazyNumberImpl(eval: () => null, getString: () => null); 111 | 112 | /// Construct a LazyNumber from a BigDecimal 113 | LazyNumber createLazyNumber(final Decimal? decimal) { 114 | return LazyNumberImpl( 115 | eval: () => decimal, getString: () => decimal.toString()); 116 | } 117 | 118 | /// Creates a new expression instance from an expression string 119 | /// 120 | /// [expression] the expression. E.g. ```2.4*sin(3)/(2-4)``` or ```sin(y)>0 & max(z, 3)>3``` 121 | Expression(String expression, 122 | {this.powerOperatorPrecedence = Expression.operatorPrecedencePower}) 123 | : _expressionString = expression { 124 | addBuiltIns(this); 125 | } 126 | 127 | /// Is the string a number? 128 | /// 129 | /// [st] The string. 130 | /// Returns `true`, if the input string is a number. 131 | bool isNumber(String st) { 132 | if (st[0] == _minusSign && st.length == 1) { 133 | return false; 134 | } 135 | if (st[0] == "+" && st.length == 1) { 136 | return false; 137 | } 138 | if (st[0] == _decimalSeparator && (st.length == 1 || !isDigit(st[1]))) { 139 | return false; 140 | } 141 | if (st[0] == 'e' || st[0] == 'E') { 142 | return false; 143 | } 144 | for (int i = 0; i < st.length; i++) { 145 | String ch = st[i]; 146 | 147 | if (!isDigit(ch) && 148 | ch != _minusSign && 149 | ch != _decimalSeparator && 150 | ch != 'e' && 151 | ch != 'E' && 152 | ch != "+") { 153 | return false; 154 | } 155 | } 156 | 157 | return true; 158 | } 159 | 160 | /// Implementation of the `Shunting Yard` algorithm to transform an infix expression to a RPN 161 | /// expression. 162 | /// 163 | /// [expression] - The input expression in infx. 164 | /// Returns a RPN representation of the expression, with each token as a list member. 165 | List _shuntingYard(String expression) { 166 | List outputQueue = []; 167 | ListQueue stack = ListQueue(); 168 | 169 | _Tokenizer tokenizer = _Tokenizer(this, expression); 170 | 171 | Token? lastFunction; 172 | Token? previousToken; 173 | 174 | while (tokenizer.moveNext()) { 175 | Token token = tokenizer.current!; 176 | 177 | switch (token.type) { 178 | case TokenType.stringParam: 179 | stack.addFirst(token); 180 | break; 181 | case TokenType.literal: 182 | case TokenType.hexLiteral: 183 | if (previousToken != null && 184 | (previousToken.type == TokenType.literal || 185 | previousToken.type == TokenType.hexLiteral)) { 186 | throw new ExpressionException.pos("Missing operator", token.pos); 187 | } 188 | outputQueue.add(token); 189 | break; 190 | case TokenType.variable: 191 | outputQueue.add(token); 192 | break; 193 | case TokenType.function: 194 | stack.addFirst(token); 195 | lastFunction = token; 196 | break; 197 | case TokenType.comma: 198 | if (previousToken != null && 199 | previousToken.type == TokenType.operator) { 200 | throw new ExpressionException.pos( 201 | missingParametersForOperator + previousToken.toString(), 202 | previousToken.pos); 203 | } 204 | while (!stack.isEmpty && stack.first.type != TokenType.openParen) { 205 | outputQueue.add(stack.removeFirst()); 206 | } 207 | if (stack.isEmpty) { 208 | if (lastFunction == null) { 209 | throw new ExpressionException.pos("Unexpected comma", token.pos); 210 | } 211 | } 212 | break; 213 | case TokenType.operator: 214 | if (previousToken != null && 215 | operators.containsKey(token.surface) && 216 | (previousToken.type == TokenType.comma || 217 | previousToken.type == TokenType.openParen)) { 218 | if (!operators[token.surface]!.isUnaryOperator()) { 219 | throw new ExpressionException.pos( 220 | missingParametersForOperator + token.toString(), token.pos); 221 | } 222 | } 223 | 224 | ILazyOperator? o1 = operators[token.surface]; 225 | if (o1 == null) { 226 | throw new ExpressionException.pos( 227 | "Unknown operator " + token.toString(), token.pos + 1); 228 | } 229 | 230 | _shuntOperators(outputQueue, stack, o1); 231 | stack.addFirst(token); 232 | break; 233 | case TokenType.unaryOperator: 234 | if (previousToken != null && 235 | previousToken.type != TokenType.operator && 236 | previousToken.type != TokenType.comma && 237 | previousToken.type != TokenType.openParen && 238 | previousToken.type != TokenType.unaryOperator) { 239 | throw new ExpressionException.pos( 240 | "Invalid position for unary operator " + token.toString(), 241 | token.pos); 242 | } 243 | ILazyOperator? o1 = operators[token.surface]; 244 | if (o1 == null) { 245 | throw new ExpressionException.pos( 246 | "Unknown unary operator " + 247 | token.surface.substring(0, token.surface.length - 1), 248 | token.pos + 1); 249 | } 250 | 251 | _shuntOperators(outputQueue, stack, o1); 252 | stack.addFirst(token); 253 | break; 254 | case TokenType.openParen: 255 | if (previousToken != null) { 256 | if (previousToken.type == TokenType.literal || 257 | previousToken.type == TokenType.closeParen || 258 | previousToken.type == TokenType.variable || 259 | previousToken.type == TokenType.hexLiteral) { 260 | // Implicit multiplication, e.g. 23(a+b) or (a+b)(a-b) 261 | Token multiplication = new Token(); 262 | multiplication.append("*"); 263 | multiplication.type = TokenType.operator; 264 | stack.addFirst(multiplication); 265 | } 266 | // if the ( is preceded by a valid function, then it 267 | // denotes the start of a parameter list 268 | if (previousToken.type == TokenType.function) { 269 | outputQueue.add(token); 270 | } 271 | } 272 | stack.addFirst(token); 273 | break; 274 | case TokenType.closeParen: 275 | if (previousToken != null && 276 | previousToken.type == TokenType.operator && 277 | !operators[previousToken.surface]!.isUnaryOperator()) { 278 | throw new ExpressionException.pos( 279 | missingParametersForOperator + previousToken.toString(), 280 | previousToken.pos); 281 | } 282 | while (!stack.isEmpty && stack.first.type != TokenType.openParen) { 283 | outputQueue.add(stack.removeFirst()); 284 | } 285 | if (stack.isEmpty) { 286 | throw new ExpressionException("Mismatched parentheses"); 287 | } 288 | stack.removeFirst(); 289 | if (!stack.isEmpty && stack.first.type == TokenType.function) { 290 | outputQueue.add(stack.removeFirst()); 291 | } 292 | break; 293 | case null: 294 | default: 295 | break; 296 | } 297 | 298 | previousToken = token; 299 | } 300 | 301 | while (!stack.isEmpty) { 302 | Token element = stack.removeFirst(); 303 | if (element.type == TokenType.openParen || 304 | element.type == TokenType.closeParen) { 305 | throw new ExpressionException("Mismatched parentheses"); 306 | } 307 | outputQueue.add(element); 308 | } 309 | return outputQueue; 310 | } 311 | 312 | void _shuntOperators( 313 | List outputQueue, ListQueue stack, ILazyOperator o1) { 314 | Token? nextToken = stack.isEmpty ? null : stack.first; 315 | while (nextToken != null && 316 | (nextToken.type == TokenType.operator || 317 | nextToken.type == TokenType.unaryOperator) && 318 | ((o1.isLeftAssoc() && 319 | o1.getPrecedence() <= 320 | (operators[nextToken.surface]?.getPrecedence() ?? 0)) || 321 | (o1.getPrecedence() < 322 | (operators[nextToken.surface]?.getPrecedence() ?? 0)))) { 323 | outputQueue.add(stack.removeFirst()); 324 | nextToken = stack.isEmpty ? null : stack.first; 325 | } 326 | } 327 | 328 | /// Evaluates the expression. 329 | /// 330 | /// Returns the result of the expression. Trailing zeros are stripped. 331 | Decimal? eval() { 332 | ListQueue stack = ListQueue(); 333 | 334 | for (final Token token in getRPN()) { 335 | switch (token.type) { 336 | case TokenType.unaryOperator: 337 | { 338 | final LazyNumber value = stack.removeFirst(); 339 | LazyNumber result = new LazyNumberImpl(eval: () { 340 | return operators[token.surface]?.evalLazy(value, null).eval(); 341 | }, getString: () { 342 | return operators[token.surface] 343 | ?.evalLazy(value, null) 344 | .eval() 345 | .toString(); 346 | }); 347 | stack.addFirst(result); 348 | break; 349 | } 350 | case TokenType.operator: 351 | if (operators[token.surface]!.isUnaryOperator()) { 352 | LazyNumber value = stack.removeFirst(); 353 | LazyNumberImpl result = new LazyNumberImpl(eval: () { 354 | return operators[token.surface]!.evalLazy(value, null).eval(); 355 | }, getString: () { 356 | return operators[token.surface]! 357 | .evalLazy(value, null) 358 | .eval() 359 | .toString(); 360 | }); 361 | 362 | stack.addFirst(result); 363 | } else { 364 | final LazyNumber v1 = stack.removeFirst(); 365 | final LazyNumber v2 = stack.removeFirst(); 366 | LazyNumber result = new LazyNumberImpl(eval: () { 367 | return operators[token.surface]?.evalLazy(v2, v1).eval(); 368 | }, getString: () { 369 | return operators[token.surface] 370 | ?.evalLazy(v2, v1) 371 | .eval() 372 | .toString(); 373 | }); 374 | 375 | stack.addFirst(result); 376 | } 377 | 378 | break; 379 | case TokenType.variable: 380 | if (!variables.containsKey(token.surface)) { 381 | throw new ExpressionException( 382 | "Unknown operator or function: " + token.toString()); 383 | } 384 | 385 | stack.addFirst(LazyNumberImpl(eval: () { 386 | LazyNumber? lazyVariable = variables[token.surface]; 387 | Decimal? value = lazyVariable == null ? null : lazyVariable.eval(); 388 | return value; 389 | }, getString: () { 390 | LazyNumber? lazyVariable = variables[token.surface]; 391 | return lazyVariable?.getString(); 392 | })); 393 | break; 394 | case TokenType.function: 395 | ILazyFunction f = functions[token.surface.toUpperCase()]!; 396 | List p = []; 397 | // pop parameters off the stack until we hit the start of 398 | // this function's parameter list 399 | while (!stack.isEmpty && stack.first != _paramsStart) { 400 | p.insert(0, stack.removeFirst()); 401 | } 402 | 403 | if (stack.first == _paramsStart) { 404 | stack.removeFirst(); 405 | } 406 | 407 | LazyNumber? fResult = f.lazyEval(p); 408 | stack.addFirst(fResult); 409 | break; 410 | case TokenType.openParen: 411 | stack.addFirst(_paramsStart); 412 | break; 413 | case TokenType.literal: 414 | stack.addFirst(LazyNumberImpl(eval: () { 415 | if (token.surface.toLowerCase() == "null") { 416 | return null; 417 | } 418 | 419 | return Decimal.parse(token.surface); 420 | }, getString: () { 421 | return Decimal.parse(token.surface).toString(); 422 | })); 423 | break; 424 | case TokenType.stringParam: 425 | stack.addFirst(LazyNumberImpl(eval: () { 426 | return null; 427 | }, getString: () { 428 | return token.surface; 429 | })); 430 | break; 431 | case TokenType.hexLiteral: 432 | stack.addFirst(LazyNumberImpl(eval: () { 433 | BigInt bigInt = BigInt.parse(token.surface.substring(2), radix: 16); 434 | return Decimal.parse(bigInt.toString()); 435 | }, getString: () { 436 | BigInt bigInt = BigInt.parse(token.surface.substring(2), radix: 16); 437 | return Decimal.parse(bigInt.toString()).toString(); 438 | })); 439 | break; 440 | default: 441 | throw new ExpressionException.pos( 442 | "Unexpected token " + token.surface, token.pos); 443 | } 444 | } 445 | 446 | Decimal? result = stack.removeFirst().eval(); 447 | if (result == null) { 448 | return null; 449 | } 450 | 451 | // if (stripTrailingZeros) { 452 | // result = result.stripTrailingZeros(); 453 | // } 454 | return result; 455 | } 456 | 457 | /// Sets the characters other than letters and digits that are valid as the first character of a 458 | /// variable. 459 | /// 460 | /// [chars] - The new set of variable characters. 461 | /// Returns the expression, allows to chain methods. 462 | Expression setFirstVariableCharacters(String chars) { 463 | this._firstVarChars = chars; 464 | return this; 465 | } 466 | 467 | /// Sets the characters other than letters and digits that are valid as the second and subsequent 468 | /// characters of a variable. 469 | /// 470 | /// [chars] - The new set of variable characters. 471 | /// Returns the expression, allows to chain methods. 472 | Expression setVariableCharacters(String chars) { 473 | this._varChars = chars; 474 | return this; 475 | } 476 | 477 | /// Adds an operator to the list of supported operators. 478 | /// 479 | /// [operator] - The operator to add. 480 | /// Returns the previous operator with that name, or null if there was none. 481 | T addOperator(T operator) { 482 | String key = operator.getOper(); 483 | if (operator is AbstractUnaryOperator) { 484 | key += "u"; 485 | } 486 | operators[key] = operator; 487 | return operator; 488 | } 489 | 490 | /// Adds a function to the list of supported functions 491 | /// 492 | /// [function] - The function to add. 493 | /// Returns the previous operator with that name, or null if there was none. 494 | IFunc addFunc(IFunc function) { 495 | functions[function.getName()] = function; 496 | return function; 497 | } 498 | 499 | /// Adds a lazy function function to the list of supported functions 500 | /// 501 | /// [function] - The function to add. 502 | /// Returns the previous operator with that name, or null if there was none. 503 | ILazyFunction addLazyFunction(ILazyFunction function) { 504 | functions[function.getName()] = function; 505 | return function; 506 | } 507 | 508 | /// Sets a variable value. 509 | /// 510 | /// [variable] - The variable name. 511 | /// [value] - The variable value. 512 | /// Returns the expression, allows to chain methods. 513 | Expression setDecimalVariable(String variable, Decimal? value) { 514 | return setLazyVariable(variable, createLazyNumber(value)); 515 | } 516 | 517 | /// Sets a variable value. 518 | /// 519 | /// [variable] The variable name. 520 | /// [value] - The variable value. 521 | /// Returns the expression, allows to chain methods. 522 | Expression setLazyVariable(String variable, LazyNumber? value) { 523 | variables[variable] = value; 524 | return this; 525 | } 526 | 527 | /// Sets a variable value. 528 | /// 529 | /// [variable] - The variable to set. 530 | /// [value] - The variable value. 531 | /// Returns the expression, allows to chain methods. 532 | Expression setStringVariable(String variable, String value) { 533 | if (isNumber(value)) { 534 | variables[variable] = createLazyNumber(Decimal.parse(value)); 535 | } else if (value.toLowerCase() == "null") { 536 | variables[variable] = null; 537 | } else { 538 | final String expStr = value; 539 | variables[variable] = LazyNumberImpl(eval: () { 540 | Expression innerE = Expression(expStr); 541 | innerE.variables = variables; 542 | innerE.functions = functions; 543 | innerE.operators = operators; 544 | Decimal? val = innerE.eval(); 545 | return val; 546 | }, getString: () { 547 | return expStr; 548 | }); 549 | 550 | _rpn = null; 551 | } 552 | return this; 553 | } 554 | 555 | /// Creates a new inner expression for nested expression. 556 | /// 557 | /// [expression] The string expression. 558 | /// Returns the inner Expression instance. 559 | Expression _createEmbeddedExpression(final String expression) { 560 | final Map outerVariables = variables; 561 | final Map outerFunctions = functions; 562 | final Map outerOperators = operators; 563 | Expression exp = new Expression(expression); 564 | exp.variables = outerVariables; 565 | exp.functions = outerFunctions; 566 | exp.operators = outerOperators; 567 | return exp; 568 | } 569 | 570 | /// Get an iterator for this expression, allows iterating over an expression token by token. 571 | /// 572 | /// Returns a new iterator instance for this expression. 573 | Iterator getExpressionTokenizer() { 574 | final String expression = this._expressionString; 575 | 576 | return _Tokenizer(this, expression); 577 | } 578 | 579 | /// Cached access to the RPN notation of this expression, ensures only one calculation of the RPN 580 | /// per expression instance. If no cached instance exists, a new one will be created and put to the 581 | /// cache. 582 | /// 583 | /// Returns the cached RPN instance. 584 | List getRPN() { 585 | if (_rpn == null) { 586 | _rpn = _shuntingYard(this._expressionString); 587 | _validate(_rpn!); 588 | } 589 | return _rpn!; 590 | } 591 | 592 | /// Check that the expression has enough numbers and variables to fit the requirements of the 593 | /// operators and functions, also check for only 1 result stored at the end of the evaluation. 594 | void _validate(List rpn) { 595 | // Thanks to Norman Ramsey: 596 | // http://http://stackoverflow.com/questions/789847/postfix-notation-validation 597 | // each push on to this stack is a new function scope, with the value of 598 | // each 599 | // layer on the stack being the count of the number of parameters in 600 | // that scope 601 | ListQueue stack = ListQueue(); 602 | 603 | // push the 'global' scope 604 | stack.addFirst(0); 605 | 606 | for (final Token token in rpn) { 607 | switch (token.type) { 608 | case TokenType.unaryOperator: 609 | if (stack.first < 1) { 610 | throw new ExpressionException( 611 | missingParametersForOperator + token.toString()); 612 | } 613 | break; 614 | case TokenType.operator: 615 | ILazyOperator? op = operators[token.surface]; 616 | int numOperands = 2; 617 | if (op?.isUnaryOperator() ?? false) { 618 | numOperands = 1; 619 | } 620 | 621 | if (stack.first < numOperands) { 622 | throw new ExpressionException( 623 | missingParametersForOperator + token.toString()); 624 | } 625 | if (numOperands > 1) { 626 | // pop the operator's 2 parameters and add the result 627 | int peek = stack.removeFirst(); 628 | stack.addFirst(peek - numOperands + 1); 629 | } 630 | 631 | break; 632 | case TokenType.function: 633 | ILazyFunction? f = functions[token.surface.toUpperCase()]; 634 | if (f == null) { 635 | throw new ExpressionException.pos( 636 | "Unknown function " + token.toString(), token.pos + 1); 637 | } 638 | 639 | int numParams = stack.removeFirst(); 640 | if (!f.numParamsVaries() && numParams != f.getNumParams()) { 641 | throw new ExpressionException("Function " + 642 | token.toString() + 643 | " expected " + 644 | f.getNumParams().toString() + 645 | " parameters, got " + 646 | numParams.toString()); 647 | } 648 | if (stack.isEmpty) { 649 | throw new ExpressionException( 650 | "Too many function calls, maximum scope exceeded"); 651 | } 652 | // push the result of the function 653 | int peek = stack.removeFirst(); 654 | stack.addFirst(peek + 1); 655 | break; 656 | case TokenType.openParen: 657 | stack.addFirst(0); 658 | break; 659 | default: 660 | int peek = stack.removeFirst(); 661 | stack.addFirst(peek + 1); 662 | } 663 | } 664 | 665 | if (stack.length > 1) { 666 | throw new ExpressionException( 667 | "Too many unhandled function parameter lists"); 668 | } else if (stack.first > 1) { 669 | throw new ExpressionException("Too many numbers or variables"); 670 | } else if (stack.first < 1) { 671 | throw new ExpressionException("Empty expression"); 672 | } 673 | } 674 | 675 | /// Get a string representation of the RPN (Reverse Polish Notation) for this expression. 676 | /// 677 | /// Returns a string with the RPN representation for this expression. 678 | String toRPN() { 679 | String result = ""; 680 | for (Token t in getRPN()) { 681 | if (result.isNotEmpty) { 682 | result += " "; 683 | } 684 | if (t.type == TokenType.variable && variables.containsKey(t.surface)) { 685 | LazyNumber innerVariable = variables[t.surface]!; 686 | String innerExp = innerVariable.getString(); 687 | if (isNumber(innerExp)) { 688 | // if it is a number, then we don't 689 | // expan in the RPN 690 | result += t.toString(); 691 | } else { 692 | // expand the nested variable to its RPN representation 693 | Expression exp = _createEmbeddedExpression(innerExp); 694 | String nestedExpRpn = exp.toRPN(); 695 | result += nestedExpRpn; 696 | } 697 | } else { 698 | result += t.toString(); 699 | } 700 | } 701 | return result.toString(); 702 | } 703 | 704 | /// Checks whether the expression is a boolean expression. An expression is considered a boolean 705 | /// expression, if the last operator or function is boolean. The IF function is handled special. If 706 | /// the third parameter is boolean, then the IF is also considered boolean, else non-boolean. 707 | /// 708 | /// Returns `true` if the last operator/function was a boolean. 709 | bool isBoolean() { 710 | List rpnList = getRPN(); 711 | if (rpnList.isNotEmpty) { 712 | for (int i = rpnList.length - 1; i >= 0; i--) { 713 | Token t = rpnList[i]; 714 | 715 | /* 716 | * The IF function is handled special. If the third parameter is 717 | * boolean, then the IF is also considered a boolean. Just skip 718 | * the IF function to check the second parameter. 719 | */ 720 | if (t.surface == "IF") { 721 | continue; 722 | } 723 | if (t.type == TokenType.function) { 724 | return functions[t.surface]!.isBooleanFunction(); 725 | } else if (t.type == TokenType.operator) { 726 | return operators[t.surface]!.isBooleanOperator(); 727 | } 728 | } 729 | } 730 | return false; 731 | } 732 | 733 | /// Returns a list of the variables in the expression. 734 | /// 735 | /// Returns a list of the variable names in this expression. 736 | List getUsedVariables() { 737 | List result = []; 738 | _Tokenizer tokenizer = new _Tokenizer(this, _expressionString); 739 | while (tokenizer.moveNext()) { 740 | Token nextToken = tokenizer.current!; 741 | String token = nextToken.toString(); 742 | if (nextToken.type != TokenType.variable || 743 | token == "PI" || 744 | token == "e" || 745 | token == "TRUE" || 746 | token == "FALSE") { 747 | continue; 748 | } 749 | result.add(token); 750 | } 751 | return result; 752 | } 753 | 754 | /// Exposing declared operators in the expression. 755 | /// 756 | /// Returns all declared operators. 757 | Iterable getDeclaredOperators() { 758 | return operators.keys; 759 | } 760 | 761 | /// Exposing declared variables in the expression. 762 | /// 763 | /// Returns all declared variables. 764 | Iterable getDeclaredVariables() { 765 | return variables.keys; 766 | } 767 | 768 | /// Exposing declared functions. 769 | /// 770 | /// Returns all declared functions. 771 | Iterable getDeclaredFunctions() { 772 | return functions.keys; 773 | } 774 | } 775 | 776 | class LazyNumberImpl extends LazyNumber { 777 | final Function _eval; 778 | final Function _getString; 779 | 780 | LazyNumberImpl({required Function eval, required Function getString}) 781 | : _eval = eval, 782 | _getString = getString; 783 | 784 | @override 785 | Decimal? eval() { 786 | return _eval(); 787 | } 788 | 789 | @override 790 | String getString() { 791 | return _getString(); 792 | } 793 | } 794 | 795 | /// The expression evaluators exception class. 796 | class ExpressionException implements Exception { 797 | final String msg; 798 | 799 | const ExpressionException(this.msg); 800 | 801 | const ExpressionException.pos(String message, int characterPosition) 802 | : msg = "$message at character position $characterPosition"; 803 | 804 | @override 805 | String toString() => "ExpressionException: $msg"; 806 | } 807 | 808 | /// LazyNumber interface created for lazily evaluated functions 809 | abstract class LazyNumber { 810 | Decimal? eval(); 811 | 812 | String getString(); 813 | } 814 | 815 | enum TokenType { 816 | variable, 817 | function, 818 | literal, 819 | operator, 820 | unaryOperator, 821 | openParen, 822 | comma, 823 | closeParen, 824 | hexLiteral, 825 | stringParam 826 | } 827 | 828 | class Token { 829 | String surface = ""; 830 | TokenType? type; 831 | int pos = 0; 832 | 833 | void append(String c) => surface += c; 834 | 835 | String charAt(int pos) => surface[pos]; 836 | 837 | int length() { 838 | return surface.length; 839 | } 840 | 841 | @override 842 | String toString() { 843 | return surface; 844 | } 845 | } 846 | 847 | /// Expression tokenizer that allows to iterate over a {@link String} expression token by token. 848 | /// Blank characters will be skipped. 849 | class _Tokenizer extends Iterator { 850 | final List _hexDigits = [ 851 | 'x', 852 | '0', 853 | '1', 854 | '2', 855 | '3', 856 | '4', 857 | '5', 858 | '6', 859 | '7', 860 | '8', 861 | '9', 862 | 'a', 863 | 'b', 864 | 'c', 865 | 'd', 866 | 'e', 867 | 'f' 868 | ]; 869 | 870 | Expression _expression; 871 | 872 | /// Actual position in expression string. 873 | int pos = 0; 874 | 875 | /// The original input expression. 876 | String input; 877 | 878 | /// The previous token or null if none. 879 | Token? previousToken; 880 | 881 | /// The next token, or null if none 882 | Token? nextToken = new Token(); 883 | 884 | /// Creates a new tokenizer for an expression. 885 | /// 886 | /// [input] The expression string. 887 | _Tokenizer(this._expression, String input) : input = input.trim(); 888 | 889 | /// Peek at the next character, without advancing the iterator. 890 | /// 891 | /// Returns the next character or character 0, if at end of string. 892 | String? peekNextChar() { 893 | if (pos < (input.length - 1)) { 894 | return input[pos + 1]; 895 | } else { 896 | return null; 897 | } 898 | } 899 | 900 | bool isHexDigit(String ch) { 901 | return _hexDigits.any((e) => e == ch || e.toUpperCase() == ch); 902 | } 903 | 904 | @override 905 | Token? get current { 906 | return previousToken; 907 | } 908 | 909 | @override 910 | bool moveNext() { 911 | Token token = new Token(); 912 | 913 | if (pos >= input.length) { 914 | previousToken = null; 915 | return false; 916 | } 917 | String ch = input[pos]; 918 | while (isWhitespace(ch) && pos < input.length) { 919 | ch = input[++pos]; 920 | } 921 | token.pos = pos; 922 | 923 | bool isHex = false; 924 | 925 | if (isDigit(ch) || 926 | (ch == Expression._decimalSeparator && isDigit(peekNextChar()))) { 927 | if (ch == '0' && (peekNextChar() == 'x' || peekNextChar() == 'X')) { 928 | isHex = true; 929 | } 930 | while ((isHex && isHexDigit(ch)) || 931 | (isDigit(ch) || 932 | ch == Expression._decimalSeparator || 933 | ch == 'e' || 934 | ch == 'E' || 935 | (ch == Expression._minusSign && 936 | token.length() > 0 && 937 | ('e' == token.charAt(token.length() - 1) || 938 | 'E' == token.charAt(token.length() - 1))) || 939 | (ch == '+' && 940 | token.length() > 0 && 941 | ('e' == token.charAt(token.length() - 1) || 942 | 'E' == token.charAt(token.length() - 1)))) && 943 | (pos < input.length)) { 944 | token.append(input[pos++]); 945 | ch = pos == input.length ? "" : input[pos]; 946 | } 947 | token.type = isHex ? TokenType.hexLiteral : TokenType.literal; 948 | } else if (ch == '"') { 949 | pos++; 950 | if (previousToken?.type != TokenType.stringParam) { 951 | ch = input[pos]; 952 | while (ch != '"') { 953 | token.append(input[pos++]); 954 | ch = pos == input.length ? "" : input[pos]; 955 | } 956 | token.type = TokenType.stringParam; 957 | } else { 958 | return moveNext(); 959 | } 960 | } else if (isLetter(ch) || _expression._firstVarChars.indexOf(ch) >= 0) { 961 | while ((isLetter(ch) || 962 | isDigit(ch) || 963 | _expression._varChars.indexOf(ch) >= 0 || 964 | token.length() == 0 && 965 | _expression._firstVarChars.indexOf(ch) >= 0) && 966 | (pos < input.length)) { 967 | token.append(input[pos++]); 968 | ch = pos == input.length ? "" : input[pos]; 969 | } 970 | // Remove optional white spaces after function or variable name 971 | if (isWhitespace(ch)) { 972 | while (isWhitespace(ch) && pos < input.length) { 973 | ch = input[pos++]; 974 | } 975 | pos--; 976 | } 977 | if (_expression.operators.containsKey(token.surface)) { 978 | token.type = TokenType.operator; 979 | } else if (ch == '(') { 980 | token.type = TokenType.function; 981 | } else { 982 | token.type = TokenType.variable; 983 | } 984 | } else if (ch == '(' || ch == ')' || ch == ',') { 985 | if (ch == '(') { 986 | token.type = TokenType.openParen; 987 | } else if (ch == ')') { 988 | token.type = TokenType.closeParen; 989 | } else { 990 | token.type = TokenType.comma; 991 | } 992 | token.append(ch); 993 | pos++; 994 | } else { 995 | String greedyMatch = ""; 996 | int initialPos = pos; 997 | ch = input[pos]; 998 | int validOperatorSeenUntil = -1; 999 | while (!isLetter(ch) && 1000 | !isDigit(ch) && 1001 | _expression._firstVarChars.indexOf(ch) < 0 && 1002 | !isWhitespace(ch) && 1003 | ch != '(' && 1004 | ch != ')' && 1005 | ch != ',' && 1006 | (pos < input.length)) { 1007 | greedyMatch += ch; 1008 | pos++; 1009 | if (_expression.operators.containsKey(greedyMatch)) { 1010 | validOperatorSeenUntil = pos; 1011 | } 1012 | ch = pos == input.length ? "" : input[pos]; 1013 | } 1014 | if (validOperatorSeenUntil != -1) { 1015 | token.append(input.substring(initialPos, validOperatorSeenUntil)); 1016 | pos = validOperatorSeenUntil; 1017 | } else { 1018 | token.append(greedyMatch); 1019 | } 1020 | 1021 | if (previousToken == null || 1022 | (previousToken!.type == TokenType.operator && 1023 | !_expression.operators[previousToken!.surface]! 1024 | .isUnaryOperator()) || 1025 | previousToken!.type == TokenType.openParen || 1026 | previousToken!.type == TokenType.comma || 1027 | previousToken!.type == TokenType.unaryOperator) { 1028 | token.surface += "u"; 1029 | token.type = TokenType.unaryOperator; 1030 | } else { 1031 | token.type = TokenType.operator; 1032 | } 1033 | } 1034 | previousToken = token; 1035 | return true; 1036 | } 1037 | } 1038 | --------------------------------------------------------------------------------