├── global.json ├── src ├── NCalcAsync │ ├── NumberConversionTypePreference.cs │ ├── EvaluateFunctionAsyncHandler.cs │ ├── EvaluateParameterAsyncHandler.cs │ ├── EvaluationException.cs │ ├── Domain │ │ ├── Parameter.cs │ │ ├── Function.cs │ │ ├── LogicalExpressionVisitor.cs │ │ ├── UnaryExpression.cs │ │ ├── TernaryExpression.cs │ │ ├── BinaryExpression.cs │ │ ├── Value.cs │ │ ├── LogicalExpression.cs │ │ └── EvaluationVisitor.cs │ ├── ParameterArgs.cs │ ├── FunctionArgs.cs │ ├── ErrorListeners.cs │ ├── EvaluationOption.cs │ ├── NCalcAsync.csproj │ ├── NCalcListener.cs │ ├── Expression.cs │ ├── NCalcBaseListener.cs │ └── NCalcLexer.cs └── Grammar │ └── NCalc.g ├── .config └── dotnet-tools.json ├── Directory.Build.props ├── .editorconfig ├── README.md ├── version.json ├── test └── NCalcAsync.Tests │ ├── run-code-coverage.ps1 │ ├── NCalcAsync.Tests.csproj │ ├── coverlet.runsettings │ └── Fixtures.cs ├── .github └── workflows │ ├── ci.yml │ └── publish-nuget.yml ├── LICENSE ├── CHANGELOG.md ├── CONTRIBUTING.md ├── NCalcAsync.sln └── .gitignore /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "7.0.200", 4 | "rollForward": "latestFeature" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/NCalcAsync/NumberConversionTypePreference.cs: -------------------------------------------------------------------------------- 1 | namespace NCalcAsync 2 | { 3 | public enum NumberConversionTypePreference 4 | { 5 | Decimal = 0, 6 | Double = 1 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/NCalcAsync/EvaluateFunctionAsyncHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace NCalcAsync 4 | { 5 | public delegate Task EvaluateFunctionAsyncHandler(string name, FunctionArgs args); 6 | } 7 | -------------------------------------------------------------------------------- /src/NCalcAsync/EvaluateParameterAsyncHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace NCalcAsync 4 | { 5 | public delegate Task EvaluateParameterAsyncHandler(string name, ParameterArgs args); 6 | } 7 | -------------------------------------------------------------------------------- /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-outdated-tool": { 6 | "version": "4.5.1", 7 | "commands": [ 8 | "dotnet-outdated" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 3.0.28 6 | all 7 | 8 | 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CS4014: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call. 4 | dotnet_diagnostic.CS4014.severity = error 5 | 6 | # CS3021: Type or member does not need a CLSCompliant attribute because the assembly does not have a CLSCompliant attribute 7 | dotnet_diagnostic.CS3021.severity = silent 8 | -------------------------------------------------------------------------------- /src/NCalcAsync/EvaluationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NCalcAsync 4 | { 5 | public class EvaluationException : ApplicationException 6 | { 7 | public EvaluationException(string message) 8 | : base(message) 9 | { 10 | } 11 | 12 | public EvaluationException(string message, Exception innerException) 13 | : base(message, innerException) 14 | { 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/NCalcAsync/Domain/Parameter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace NCalcAsync.Domain 4 | { 5 | public class Identifier : LogicalExpression 6 | { 7 | public Identifier(string name) 8 | { 9 | Name = name; 10 | } 11 | 12 | public string Name { get; set; } 13 | 14 | 15 | public override async Task AcceptAsync(LogicalExpressionVisitor visitor) 16 | { 17 | await visitor.VisitAsync(this); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/NCalcAsync/ParameterArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NCalcAsync 4 | { 5 | public class ParameterArgs : EventArgs 6 | { 7 | private object _result; 8 | public object Result 9 | { 10 | get { return _result; } 11 | set 12 | { 13 | _result = value; 14 | HasResult = true; 15 | } 16 | } 17 | 18 | public bool HasResult { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NCalcAsync 2 | 3 | NCalcAsync is a fully async port of the [NCalc](https://github.com/ncalc/ncalc) mathematical expressions evaluator in .NET. NCalc can parse any expression and evaluate the result, including static or dynamic parameters and custom functions. Originally created by [@petli](https://github.com/petli). 4 | 5 | > [!CAUTION] 6 | > ⚠️This repository is discontinued and merged at NCalc main repo, please go to [NCalc main repository](https://github.com/ncalc/ncalc) to receive support.⚠️ 7 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", 3 | "version": "4.1-alpha", 4 | "publicReleaseRefSpec": [ 5 | "^refs/heads/master$", 6 | "^refs/heads/release/v\\d+\\.\\d+$", 7 | "^refs/tags/v\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9]+)?$" 8 | ], 9 | "cloudBuild": { 10 | "buildNumber": { 11 | "enabled": true 12 | } 13 | }, 14 | "release": { 15 | "branchName": "release/v{version}" 16 | } 17 | } -------------------------------------------------------------------------------- /test/NCalcAsync.Tests/run-code-coverage.ps1: -------------------------------------------------------------------------------- 1 | $config = 'Release' 2 | 3 | if (Test-Path coverage) { Remove-Item coverage -Recurse } 4 | if (Test-Path coverage.zip) { Remove-Item coverage.zip } 5 | 6 | dotnet tool install dotnet-reportgenerator-globaltool --tool-path bin 7 | 8 | $coverage_file = 'coverage.opencover.xml' 9 | 10 | dotnet test -c $config --settings "$PWD/coverlet.runsettings" "$PWD/NCalcAsync.Tests.csproj" 11 | 12 | $report_args = @('-verbosity:Info', '-reporttypes:Html') 13 | ./bin/reportgenerator @report_args "-verbosity:Info" "-reporttypes:Html" "-reports:$PWD/coverage/*/$coverage_file" "-targetdir:$PWD/coverage/" 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - release/* 8 | pull_request: 9 | branches: 10 | - master 11 | - release/* 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | with: 21 | fetch-depth: 0 22 | - name: Setup .NET 23 | uses: actions/setup-dotnet@v3 24 | - name: Restore dependencies 25 | run: dotnet restore 26 | - name: Build 27 | run: dotnet build --no-restore 28 | - name: Test 29 | run: dotnet test --no-build --verbosity normal 30 | -------------------------------------------------------------------------------- /src/NCalcAsync/Domain/Function.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace NCalcAsync.Domain 4 | { 5 | public class Function : LogicalExpression 6 | { 7 | public Function(Identifier identifier, LogicalExpression[] expressions) 8 | { 9 | Identifier = identifier; 10 | Expressions = expressions; 11 | } 12 | 13 | public Identifier Identifier { get; set; } 14 | 15 | public LogicalExpression[] Expressions { get; set; } 16 | 17 | public override async Task AcceptAsync(LogicalExpressionVisitor visitor) 18 | { 19 | await visitor.VisitAsync(this); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/NCalcAsync/Domain/LogicalExpressionVisitor.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace NCalcAsync.Domain 4 | { 5 | public abstract class LogicalExpressionVisitor 6 | { 7 | public abstract Task VisitAsync(LogicalExpression expression); 8 | public abstract Task VisitAsync(TernaryExpression expression); 9 | public abstract Task VisitAsync(BinaryExpression expression); 10 | public abstract Task VisitAsync(UnaryExpression expression); 11 | public abstract Task VisitAsync(ValueExpression expression); 12 | public abstract Task VisitAsync(Function function); 13 | public abstract Task VisitAsync(Identifier function); 14 | } 15 | } -------------------------------------------------------------------------------- /.github/workflows/publish-nuget.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Nuget 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | - name: Setup .NET 17 | uses: actions/setup-dotnet@v3 18 | - name: Restore dependencies 19 | run: dotnet restore 20 | - name: Build 21 | run: dotnet build -c Release --no-restore 22 | - name: Test 23 | run: dotnet test -c Release --no-build --verbosity normal 24 | - name: Pack 25 | run: dotnet pack -c Release --no-build 26 | - name: Publish 27 | run: dotnet nuget push src/NCalcAsync/bin/Release/NCalcAsync*.nupkg -k "${{ secrets.NUGET_API_TOKEN }}" -s https://api.nuget.org/v3/index.json 28 | -------------------------------------------------------------------------------- /src/NCalcAsync/Domain/UnaryExpression.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace NCalcAsync.Domain 4 | { 5 | public class UnaryExpression : LogicalExpression 6 | { 7 | public UnaryExpression(UnaryExpressionType type, LogicalExpression expression) 8 | { 9 | Type = type; 10 | Expression = expression; 11 | } 12 | 13 | public LogicalExpression Expression { get; set; } 14 | 15 | public UnaryExpressionType Type { get; set; } 16 | 17 | public override async Task AcceptAsync(LogicalExpressionVisitor visitor) 18 | { 19 | await visitor.VisitAsync(this); 20 | } 21 | } 22 | 23 | public enum UnaryExpressionType 24 | { 25 | Not, 26 | Negate, 27 | BitwiseNot, 28 | Positive 29 | } 30 | } -------------------------------------------------------------------------------- /src/NCalcAsync/Domain/TernaryExpression.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace NCalcAsync.Domain 4 | { 5 | public class TernaryExpression : LogicalExpression 6 | { 7 | public TernaryExpression(LogicalExpression leftExpression, LogicalExpression middleExpression, LogicalExpression rightExpression) 8 | { 9 | this.LeftExpression = leftExpression; 10 | this.MiddleExpression = middleExpression; 11 | this.RightExpression = rightExpression; 12 | } 13 | 14 | public LogicalExpression LeftExpression { get; set; } 15 | 16 | public LogicalExpression MiddleExpression { get; set; } 17 | 18 | public LogicalExpression RightExpression { get; set; } 19 | 20 | public override async Task AcceptAsync(LogicalExpressionVisitor visitor) 21 | { 22 | await visitor.VisitAsync(this); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /test/NCalcAsync.Tests/NCalcAsync.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | all 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/NCalcAsync/FunctionArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace NCalcAsync 5 | { 6 | public class FunctionArgs : EventArgs 7 | { 8 | private object _result; 9 | 10 | public object Result 11 | { 12 | get { return _result; } 13 | set 14 | { 15 | _result = value; 16 | HasResult = true; 17 | } 18 | } 19 | 20 | public bool HasResult { get; set; } 21 | 22 | private Expression[] _parameters = new Expression[0]; 23 | 24 | public Expression[] Parameters 25 | { 26 | get { return _parameters; } 27 | set { _parameters = value; } 28 | } 29 | 30 | public async Task EvaluateParametersAsync() 31 | { 32 | var values = new object[_parameters.Length]; 33 | for (int i = 0; i < values.Length; i++) 34 | { 35 | values[i] = await _parameters[i].EvaluateAsync(); 36 | } 37 | 38 | return values; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2011 Sebastien Ros 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/NCalcAsync/ErrorListeners.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using Antlr4.Runtime; 4 | 5 | namespace NCalcAsync 6 | { 7 | public class ErrorListenerParser : IAntlrErrorListener 8 | { 9 | public readonly List Errors = new List(); 10 | 11 | public void SyntaxError(TextWriter output, IRecognizer recognizer, IToken offendingSymbol, int line, int charPositionInLine, string msg, 12 | RecognitionException e) 13 | { 14 | string errorMessage = $"{msg} at {line}:{charPositionInLine + 1}"; 15 | Errors.Add(errorMessage); 16 | } 17 | } 18 | 19 | public class ErrorListenerLexer : IAntlrErrorListener 20 | { 21 | public readonly List Errors = new List(); 22 | 23 | public void SyntaxError(TextWriter output, IRecognizer recognizer, int offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e) 24 | { 25 | string errorMessage = $"{msg} at {line}:{charPositionInLine + 1}"; 26 | Errors.Add(errorMessage); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/NCalcAsync/EvaluationOption.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NCalcAsync 4 | { 5 | // Summary: 6 | // Provides enumerated values to use to set evaluation options. 7 | [Flags] 8 | public enum EvaluateOptions 9 | { 10 | // Summary: 11 | // Specifies that no options are set. 12 | None = 1, 13 | // 14 | // Summary: 15 | // Specifies case-insensitive matching. 16 | IgnoreCase = 2, 17 | // 18 | // Summary: 19 | // No-cache mode. Ingores any pre-compiled expression in the cache. 20 | NoCache = 4, 21 | // 22 | // Summary: 23 | // Treats parameters as arrays and result a set of results. 24 | IterateParameters = 8, 25 | // 26 | // Summary: 27 | // When using Round(), if a number is halfway between two others, it is rounded toward the nearest number that is away from zero. 28 | RoundAwayFromZero = 16, 29 | 30 | // 31 | // Summary: 32 | // Specifies the use of CaseInsensitiveComparer for comparasions. 33 | CaseInsensitiveComparer = 32 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/NCalcAsync.Tests/coverlet.runsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1 5 | ./coverage/ 6 | 7 | 8 | 9 | 10 | 11 | 12 | opencover 13 | 14 | 15 | 16 | 17 | 18 | Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute 19 | 20 | 21 | 22 | false 23 | false 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/NCalcAsync/Domain/BinaryExpression.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace NCalcAsync.Domain 4 | { 5 | public class BinaryExpression : LogicalExpression 6 | { 7 | public BinaryExpression(BinaryExpressionType type, LogicalExpression leftExpression, LogicalExpression rightExpression) 8 | { 9 | Type = type; 10 | LeftExpression = leftExpression; 11 | RightExpression = rightExpression; 12 | } 13 | 14 | public LogicalExpression LeftExpression { get; set; } 15 | 16 | public LogicalExpression RightExpression { get; set; } 17 | 18 | public BinaryExpressionType Type { get; set; } 19 | 20 | public override async Task AcceptAsync(LogicalExpressionVisitor visitor) 21 | { 22 | await visitor.VisitAsync(this); 23 | } 24 | } 25 | 26 | public enum BinaryExpressionType 27 | { 28 | And, 29 | Or, 30 | NotEqual, 31 | LesserOrEqual, 32 | GreaterOrEqual, 33 | Lesser, 34 | Greater, 35 | Equal, 36 | Minus, 37 | Plus, 38 | Modulo, 39 | Div, 40 | Times, 41 | BitwiseOr, 42 | BitwiseAnd, 43 | BitwiseXOr, 44 | LeftShift, 45 | RightShift, 46 | Unknown, 47 | Exponentiation 48 | } 49 | } -------------------------------------------------------------------------------- /src/NCalcAsync/NCalcAsync.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | petli 6 | https://github.com/ncalc 7 | 8 | 2011 Sebastien Ros 9 | 10 | https://github.com/ncalc/ncalc-async 11 | https://github.com/ncalc/ncalc-async.git 12 | ncalc; async 13 | NCalcAsync is a fully async port of the NCalc mathematical expressions evaluator in .NET, targeting netstandard 2.0. NCalc can parse any expression and evaluate the result, including static or dynamic parameters and custom functions. 14 | false 15 | MIT 16 | true 17 | snupkg 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 4.0: 2 | 3 | * [Update ANTLR to v4 to remove transient targeting of NETStandard 1.6.0, which caused problems when publishing solutions on a modern .NET](https://github.com/ncalc/ncalc-async/pull/18) (Thanks @markcanary!) 4 | 5 | # 3.2: 6 | 7 | * [Flag to control if string parameters should be converted to decimal (the default) or double](https://github.com/ncalc/ncalc-async/pull/16) 8 | 9 | # 3.1: 10 | 11 | * [New function `Atan2`](https://github.com/ncalc/ncalc-async/pull/15) 12 | 13 | # 3.0: 14 | 15 | Several syntax changes to the grammar: 16 | 17 | * [Exponentiation operator `**`](https://github.com/ncalc/ncalc-async/issues/10) 18 | * [Case insensitive operators and key words (e.g. `AND` or `True`)](https://github.com/ncalc/ncalc/issues/37) 19 | * [Support numbers with trailing dot (e.g. `47.`)](https://github.com/ncalc/ncalc/issues/21) 20 | * [Support for positive sign (e.g. `+5`)](https://github.com/ncalc/ncalc/issues/11) 21 | 22 | While these changes in themselves wouldn't introduce compatibility issues with previously valid statements, code that relies on statements with these constructs being invalid would be affected. The grammar also had to be regenerated with a new version of ANTLR with some fixes to it since it was clear that the generated source code had been modified manually. Manual review indicates that the regenerated grammar is identical, but because of both these reasons this is released as a new major version. 23 | 24 | # 2.1: 25 | 26 | * [Bugfix: invalid tokens were skipped silently without any errors](https://github.com/ncalc/ncalc-async/issues/6). Expressions like `"4711"` would ignore the `"` (since that is not the string character in the NCalc syntax) and parse it as the number `4711`, but now an `EvaluationException` is thrown as for other syntax issues. This may affect existing expressions, but since they were always incorrect and now give an exception rather than silently getting a new value it does not merit a new major release. 27 | 28 | # 2.0 29 | 30 | * [Major bugfix: long integers are now treated as integers](https://github.com/ncalc/ncalc-async/issues/4). Previous versions converted them to single-precision floats, which caused data loss on large numbers. Since this affects the results of existing expressions, it requires a new major release. 31 | 32 | # 1.2 33 | 34 | * [New builtin function `Ln()`](https://github.com/ncalc/ncalc-async/pull/2) 35 | 36 | # 1.1 37 | 38 | * [Handle += for `Expression.EvaluateFunctionAsync` and `Expression.EvaluateParameterAsync`](https://github.com/ncalc/ncalc-async/issues/1) 39 | * Parameter names are changed to lowercase if `EvaluateOptions.IgnoreCase` is set (previously only function names were lowercased) 40 | 41 | # 1.0 42 | 43 | Initial public release. 44 | -------------------------------------------------------------------------------- /src/NCalcAsync/Domain/Value.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace NCalcAsync.Domain 5 | { 6 | public class ValueExpression : LogicalExpression 7 | { 8 | public ValueExpression(object value, ValueType type) 9 | { 10 | Value = value; 11 | Type = type; 12 | } 13 | 14 | public ValueExpression(object value) 15 | { 16 | switch (System.Type.GetTypeCode(value.GetType())) 17 | { 18 | case TypeCode.Boolean : 19 | Type = ValueType.Boolean; 20 | break; 21 | 22 | case TypeCode.DateTime : 23 | Type = ValueType.DateTime; 24 | break; 25 | 26 | case TypeCode.Decimal: 27 | case TypeCode.Double: 28 | case TypeCode.Single: 29 | Type = ValueType.Float; 30 | break; 31 | 32 | case TypeCode.Byte: 33 | case TypeCode.SByte: 34 | case TypeCode.Int16: 35 | case TypeCode.Int32: 36 | case TypeCode.Int64: 37 | case TypeCode.UInt16: 38 | case TypeCode.UInt32: 39 | case TypeCode.UInt64: 40 | Type = ValueType.Integer; 41 | break; 42 | 43 | case TypeCode.String: 44 | Type = ValueType.String; 45 | break; 46 | 47 | default: 48 | throw new EvaluationException("This value could not be handled: " + value); 49 | } 50 | 51 | Value = value; 52 | } 53 | 54 | public ValueExpression(string value) 55 | { 56 | Value = value; 57 | Type = ValueType.String; 58 | } 59 | 60 | public ValueExpression(int value) 61 | { 62 | Value = value; 63 | Type = ValueType.Integer; 64 | } 65 | 66 | public ValueExpression(long value) 67 | { 68 | Value = value; 69 | Type = ValueType.Integer; 70 | } 71 | 72 | public ValueExpression(double value) 73 | { 74 | Value = value; 75 | Type = ValueType.Float; 76 | } 77 | 78 | public ValueExpression(DateTime value) 79 | { 80 | Value = value; 81 | Type = ValueType.DateTime; 82 | } 83 | 84 | public ValueExpression(bool value) 85 | { 86 | Value = value; 87 | Type = ValueType.Boolean; 88 | } 89 | 90 | public object Value { get; set; } 91 | public ValueType Type { get; set; } 92 | 93 | 94 | public override async Task AcceptAsync(LogicalExpressionVisitor visitor) 95 | { 96 | await visitor.VisitAsync(this); 97 | } 98 | } 99 | 100 | public enum ValueType 101 | { 102 | Integer, 103 | String, 104 | DateTime, 105 | Float, 106 | Boolean 107 | } 108 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to NCalcAsync 2 | 3 | As with any open source projects, contributions are most welcome! 4 | 5 | # Pull requests 6 | 7 | Generally pull requests should go to the `master` branch where all new development and bugfixes are integrated. An exception are bug fixes that only apply to a specific version, which can be targeted to the relevant `release/vX.Y` branch. 8 | 9 | 10 | # Testing 11 | 12 | Run the unit tests in `NCalcAsync.Tests` to test the project. New features should always have corresponding unit tests that exercise them. 13 | 14 | 15 | # Code coverage 16 | 17 | [Coverlet](https://github.com/tonerdo/coverlet/) is used to collect code coverage when running the unit tests, using the VSTest integration. 18 | 19 | The script `run-code-coverage.ps1` will run the tests with code coverage enabled and then produce an HTML report. 20 | 21 | 22 | # Building and publishing packages 23 | 24 | Github Actions are used for CI builds in all pull requests and on new commits to `master`, and for publishing packages (see below). 25 | 26 | ## Versioning 27 | 28 | [Nerdbank.GitVersioning](https://github.com/AArnott/Nerdbank.GitVersioning) is used to assign version numbers to the packages using a standard `major.minor.patch[-alpha]` scheme. Patch versions may only contain bugfixes. New backward compatible features require a new minor version, and any breaking changes require a new major version. 29 | 30 | The [`nbgv`](https://github.com/AArnott/Nerdbank.GitVersioning/blob/master/doc/nbgv-cli.md) tool is used to manage the git branches and tags and to update `version.json`. 31 | 32 | `master` is used to integrates new features and bugfixes. Any builds here will be labelled `X.Y.Z-alpha`. 33 | 34 | `release/vX.Y` branches contain the commits that are published as non-alpha versions, and any builds in them will get a `X.Y.Z` version number. 35 | 36 | 37 | ## Creating a new major versions 38 | 39 | Any non-backward compatible features require a new major release. This should be created from `master` with `nbgv`: 40 | 41 | git checkout master 42 | nbgv set-version X.0-alpha 43 | 44 | Commit the new `version.json`. Create a branch for the new major release: 45 | 46 | nbgv prepare-release 47 | 48 | `nbgv` will create a new `release/vX.Y` branch and update `version.json` in both that branch and in `master`. 49 | Check that it looks correct and then push both `master` and the new branch to this repository to trigger builds. 50 | Follow the publishing process below to tag and publish the release. 51 | 52 | 53 | ## Creating a new minor versions 54 | 55 | New backward-compatible features should always be published in a new minor release. This should be created from `master` with `nbgv`: 56 | 57 | git checkout master 58 | nbgv prepare-release 59 | 60 | `nbgv` will create a new `release/vX.Y` branch and update `version.json` in both that branch and in `master`. 61 | Check that it looks correct and then push both `master` and the new branch to this repository to trigger builds. 62 | Follow the publishing process below to tag and publish the release. 63 | 64 | 65 | ## Creating a new patch release 66 | 67 | Just push commits to the relevant `release/vX.Y` branch, and follow the publishing process below to tag and publish the release. 68 | 69 | 70 | ## Creating a new alpha release 71 | 72 | The current `master` can be released as an alpha by following the publishing process below. 73 | 74 | 75 | ## Publishing to NuGet 76 | 77 | * Create and tag the new version with `nbgv`: 78 | ** Alpha releases: `nbgv tag master` 79 | ** Other releases: `nbgv tag release/vX.Y` 80 | * Push the tag to Github. 81 | * Go to https://github.com/ncalc/ncalc-async/tags and select the tag. 82 | * Click `Create release from tag` and copy the release notes from `CHANGELOG.md`, and publish the release. 83 | * A Github Action will run to package the library and publish it to Nuget. 84 | -------------------------------------------------------------------------------- /NCalcAsync.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29519.181 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B40C6971-2BB5-4A29-B23F-185D57F1FFAC}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NCalcAsync", "src\NCalcAsync\NCalcAsync.csproj", "{9B98E1F0-BABC-4873-8A4D-03500130627B}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{6AF67C9D-E984-43B2-B090-7B6E6817C76B}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NCalcAsync.Tests", "test\NCalcAsync.Tests\NCalcAsync.Tests.csproj", "{F1356D88-8995-4ACD-ACB5-ACE448ADCF0A}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Grammar", "Grammar", "{737B1198-F386-45F5-AA69-12C47E37FBA6}" 15 | ProjectSection(SolutionItems) = preProject 16 | src\Grammar\NCalc.g = src\Grammar\NCalc.g 17 | EndProjectSection 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8DA0001E-067C-4637-9059-414F6F25A565}" 20 | ProjectSection(SolutionItems) = preProject 21 | .editorconfig = .editorconfig 22 | .gitignore = .gitignore 23 | CHANGELOG.md = CHANGELOG.md 24 | CONTRIBUTING.md = CONTRIBUTING.md 25 | Directory.Build.props = Directory.Build.props 26 | README.md = README.md 27 | version.json = version.json 28 | EndProjectSection 29 | EndProject 30 | Global 31 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 32 | Debug|Any CPU = Debug|Any CPU 33 | Debug|x64 = Debug|x64 34 | Debug|x86 = Debug|x86 35 | Release|Any CPU = Release|Any CPU 36 | Release|x64 = Release|x64 37 | Release|x86 = Release|x86 38 | EndGlobalSection 39 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 40 | {9B98E1F0-BABC-4873-8A4D-03500130627B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {9B98E1F0-BABC-4873-8A4D-03500130627B}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {9B98E1F0-BABC-4873-8A4D-03500130627B}.Debug|x64.ActiveCfg = Debug|Any CPU 43 | {9B98E1F0-BABC-4873-8A4D-03500130627B}.Debug|x64.Build.0 = Debug|Any CPU 44 | {9B98E1F0-BABC-4873-8A4D-03500130627B}.Debug|x86.ActiveCfg = Debug|Any CPU 45 | {9B98E1F0-BABC-4873-8A4D-03500130627B}.Debug|x86.Build.0 = Debug|Any CPU 46 | {9B98E1F0-BABC-4873-8A4D-03500130627B}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {9B98E1F0-BABC-4873-8A4D-03500130627B}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {9B98E1F0-BABC-4873-8A4D-03500130627B}.Release|x64.ActiveCfg = Release|Any CPU 49 | {9B98E1F0-BABC-4873-8A4D-03500130627B}.Release|x64.Build.0 = Release|Any CPU 50 | {9B98E1F0-BABC-4873-8A4D-03500130627B}.Release|x86.ActiveCfg = Release|Any CPU 51 | {9B98E1F0-BABC-4873-8A4D-03500130627B}.Release|x86.Build.0 = Release|Any CPU 52 | {F1356D88-8995-4ACD-ACB5-ACE448ADCF0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {F1356D88-8995-4ACD-ACB5-ACE448ADCF0A}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {F1356D88-8995-4ACD-ACB5-ACE448ADCF0A}.Debug|x64.ActiveCfg = Debug|Any CPU 55 | {F1356D88-8995-4ACD-ACB5-ACE448ADCF0A}.Debug|x64.Build.0 = Debug|Any CPU 56 | {F1356D88-8995-4ACD-ACB5-ACE448ADCF0A}.Debug|x86.ActiveCfg = Debug|Any CPU 57 | {F1356D88-8995-4ACD-ACB5-ACE448ADCF0A}.Debug|x86.Build.0 = Debug|Any CPU 58 | {F1356D88-8995-4ACD-ACB5-ACE448ADCF0A}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {F1356D88-8995-4ACD-ACB5-ACE448ADCF0A}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {F1356D88-8995-4ACD-ACB5-ACE448ADCF0A}.Release|x64.ActiveCfg = Release|Any CPU 61 | {F1356D88-8995-4ACD-ACB5-ACE448ADCF0A}.Release|x64.Build.0 = Release|Any CPU 62 | {F1356D88-8995-4ACD-ACB5-ACE448ADCF0A}.Release|x86.ActiveCfg = Release|Any CPU 63 | {F1356D88-8995-4ACD-ACB5-ACE448ADCF0A}.Release|x86.Build.0 = Release|Any CPU 64 | EndGlobalSection 65 | GlobalSection(SolutionProperties) = preSolution 66 | HideSolutionNode = FALSE 67 | EndGlobalSection 68 | GlobalSection(NestedProjects) = preSolution 69 | {9B98E1F0-BABC-4873-8A4D-03500130627B} = {B40C6971-2BB5-4A29-B23F-185D57F1FFAC} 70 | {F1356D88-8995-4ACD-ACB5-ACE448ADCF0A} = {6AF67C9D-E984-43B2-B090-7B6E6817C76B} 71 | {737B1198-F386-45F5-AA69-12C47E37FBA6} = {B40C6971-2BB5-4A29-B23F-185D57F1FFAC} 72 | EndGlobalSection 73 | GlobalSection(ExtensibilityGlobals) = postSolution 74 | SolutionGuid = {EAA75140-97DA-490A-8068-3781DB07336D} 75 | EndGlobalSection 76 | EndGlobal 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | coverage/ 7 | 8 | # User-specific files 9 | *.rsuser 10 | *.suo 11 | *.user 12 | *.userosscache 13 | *.sln.docstates 14 | 15 | # User-specific files (MonoDevelop/Xamarin Studio) 16 | *.userprefs 17 | 18 | # Mono auto generated files 19 | mono_crash.* 20 | 21 | # Build results 22 | [Dd]ebug/ 23 | [Dd]ebugPublic/ 24 | [Rr]elease/ 25 | [Rr]eleases/ 26 | x64/ 27 | x86/ 28 | [Aa][Rr][Mm]/ 29 | [Aa][Rr][Mm]64/ 30 | bld/ 31 | [Bb]in/ 32 | [Oo]bj/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # StyleCop 67 | StyleCopReport.xml 68 | 69 | # Files built by Visual Studio 70 | *_i.c 71 | *_p.c 72 | *_h.h 73 | *.ilk 74 | *.meta 75 | *.obj 76 | *.iobj 77 | *.pch 78 | *.pdb 79 | *.ipdb 80 | *.pgc 81 | *.pgd 82 | *.rsp 83 | *.sbr 84 | *.tlb 85 | *.tli 86 | *.tlh 87 | *.tmp 88 | *.tmp_proj 89 | *_wpftmp.csproj 90 | *.log 91 | *.vspscc 92 | *.vssscc 93 | .builds 94 | *.pidb 95 | *.svclog 96 | *.scc 97 | 98 | # Chutzpah Test files 99 | _Chutzpah* 100 | 101 | # Visual C++ cache files 102 | ipch/ 103 | *.aps 104 | *.ncb 105 | *.opendb 106 | *.opensdf 107 | *.sdf 108 | *.cachefile 109 | *.VC.db 110 | *.VC.VC.opendb 111 | 112 | # Visual Studio profiler 113 | *.psess 114 | *.vsp 115 | *.vspx 116 | *.sap 117 | 118 | # Visual Studio Trace Files 119 | *.e2e 120 | 121 | # TFS 2012 Local Workspace 122 | $tf/ 123 | 124 | # Guidance Automation Toolkit 125 | *.gpState 126 | 127 | # ReSharper is a .NET coding add-in 128 | _ReSharper*/ 129 | *.[Rr]e[Ss]harper 130 | *.DotSettings.user 131 | 132 | # TeamCity is a build add-in 133 | _TeamCity* 134 | 135 | # DotCover is a Code Coverage Tool 136 | *.dotCover 137 | 138 | # AxoCover is a Code Coverage Tool 139 | .axoCover/* 140 | !.axoCover/settings.json 141 | 142 | # Visual Studio code coverage results 143 | *.coverage 144 | *.coveragexml 145 | 146 | # NCrunch 147 | _NCrunch_* 148 | .*crunch*.local.xml 149 | nCrunchTemp_* 150 | 151 | # MightyMoose 152 | *.mm.* 153 | AutoTest.Net/ 154 | 155 | # Web workbench (sass) 156 | .sass-cache/ 157 | 158 | # Installshield output folder 159 | [Ee]xpress/ 160 | 161 | # DocProject is a documentation generator add-in 162 | DocProject/buildhelp/ 163 | DocProject/Help/*.HxT 164 | DocProject/Help/*.HxC 165 | DocProject/Help/*.hhc 166 | DocProject/Help/*.hhk 167 | DocProject/Help/*.hhp 168 | DocProject/Help/Html2 169 | DocProject/Help/html 170 | 171 | # Click-Once directory 172 | publish/ 173 | 174 | # Publish Web Output 175 | *.[Pp]ublish.xml 176 | *.azurePubxml 177 | # Note: Comment the next line if you want to checkin your web deploy settings, 178 | # but database connection strings (with potential passwords) will be unencrypted 179 | *.pubxml 180 | *.publishproj 181 | 182 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 183 | # checkin your Azure Web App publish settings, but sensitive information contained 184 | # in these scripts will be unencrypted 185 | PublishScripts/ 186 | 187 | # NuGet Packages 188 | *.nupkg 189 | # NuGet Symbol Packages 190 | *.snupkg 191 | # The packages folder can be ignored because of Package Restore 192 | **/[Pp]ackages/* 193 | # except build/, which is used as an MSBuild target. 194 | !**/[Pp]ackages/build/ 195 | # Uncomment if necessary however generally it will be regenerated when needed 196 | #!**/[Pp]ackages/repositories.config 197 | # NuGet v3's project.json files produces more ignorable files 198 | *.nuget.props 199 | *.nuget.targets 200 | 201 | # Microsoft Azure Build Output 202 | csx/ 203 | *.build.csdef 204 | 205 | # Microsoft Azure Emulator 206 | ecf/ 207 | rcf/ 208 | 209 | # Windows Store app package directories and files 210 | AppPackages/ 211 | BundleArtifacts/ 212 | Package.StoreAssociation.xml 213 | _pkginfo.txt 214 | *.appx 215 | *.appxbundle 216 | *.appxupload 217 | 218 | # Visual Studio cache files 219 | # files ending in .cache can be ignored 220 | *.[Cc]ache 221 | # but keep track of directories ending in .cache 222 | !?*.[Cc]ache/ 223 | 224 | # Others 225 | ClientBin/ 226 | ~$* 227 | *~ 228 | *.dbmdl 229 | *.dbproj.schemaview 230 | *.jfm 231 | *.pfx 232 | *.publishsettings 233 | orleans.codegen.cs 234 | 235 | # Including strong name files can present a security risk 236 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 237 | #*.snk 238 | 239 | # Since there are multiple workflows, uncomment next line to ignore bower_components 240 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 241 | #bower_components/ 242 | 243 | # RIA/Silverlight projects 244 | Generated_Code/ 245 | 246 | # Backup & report files from converting an old project file 247 | # to a newer Visual Studio version. Backup files are not needed, 248 | # because we have git ;-) 249 | _UpgradeReport_Files/ 250 | Backup*/ 251 | UpgradeLog*.XML 252 | UpgradeLog*.htm 253 | ServiceFabricBackup/ 254 | *.rptproj.bak 255 | 256 | # SQL Server files 257 | *.mdf 258 | *.ldf 259 | *.ndf 260 | 261 | # Business Intelligence projects 262 | *.rdl.data 263 | *.bim.layout 264 | *.bim_*.settings 265 | *.rptproj.rsuser 266 | *- [Bb]ackup.rdl 267 | *- [Bb]ackup ([0-9]).rdl 268 | *- [Bb]ackup ([0-9][0-9]).rdl 269 | 270 | # Microsoft Fakes 271 | FakesAssemblies/ 272 | 273 | # GhostDoc plugin setting file 274 | *.GhostDoc.xml 275 | 276 | # Node.js Tools for Visual Studio 277 | .ntvs_analysis.dat 278 | node_modules/ 279 | 280 | # Visual Studio 6 build log 281 | *.plg 282 | 283 | # Visual Studio 6 workspace options file 284 | *.opt 285 | 286 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 287 | *.vbw 288 | 289 | # Visual Studio LightSwitch build output 290 | **/*.HTMLClient/GeneratedArtifacts 291 | **/*.DesktopClient/GeneratedArtifacts 292 | **/*.DesktopClient/ModelManifest.xml 293 | **/*.Server/GeneratedArtifacts 294 | **/*.Server/ModelManifest.xml 295 | _Pvt_Extensions 296 | 297 | # Paket dependency manager 298 | .paket/paket.exe 299 | paket-files/ 300 | 301 | # FAKE - F# Make 302 | .fake/ 303 | 304 | # CodeRush personal settings 305 | .cr/personal 306 | 307 | # Python Tools for Visual Studio (PTVS) 308 | __pycache__/ 309 | *.pyc 310 | 311 | # Cake - Uncomment if you are using it 312 | # tools/** 313 | # !tools/packages.config 314 | 315 | # Tabs Studio 316 | *.tss 317 | 318 | # Telerik's JustMock configuration file 319 | *.jmconfig 320 | 321 | # BizTalk build output 322 | *.btp.cs 323 | *.btm.cs 324 | *.odx.cs 325 | *.xsd.cs 326 | 327 | # OpenCover UI analysis results 328 | OpenCover/ 329 | 330 | # Azure Stream Analytics local run output 331 | ASALocalRun/ 332 | 333 | # MSBuild Binary and Structured Log 334 | *.binlog 335 | 336 | # NVidia Nsight GPU debugger configuration file 337 | *.nvuser 338 | 339 | # MFractors (Xamarin productivity tool) working folder 340 | .mfractor/ 341 | 342 | # Local History for Visual Studio 343 | .localhistory/ 344 | 345 | # BeatPulse healthcheck temp database 346 | healthchecksdb 347 | 348 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 349 | MigrationBackup/ 350 | 351 | # Ionide (cross platform F# VS Code tools) working folder 352 | .ionide/ 353 | -------------------------------------------------------------------------------- /src/NCalcAsync/Domain/LogicalExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Threading.Tasks; 4 | 5 | namespace NCalcAsync.Domain 6 | { 7 | public abstract class LogicalExpression 8 | { 9 | const char BS = '\\'; 10 | 11 | private static string extractString(string text) 12 | { 13 | 14 | StringBuilder sb = new StringBuilder(text); 15 | int startIndex = 1; // Skip initial quote 16 | int slashIndex = -1; 17 | 18 | while ((slashIndex = sb.ToString().IndexOf(BS, startIndex)) != -1) 19 | { 20 | char escapeType = sb[slashIndex + 1]; 21 | switch (escapeType) 22 | { 23 | case 'u': 24 | string hcode = String.Concat(sb[slashIndex + 4], sb[slashIndex + 5]); 25 | string lcode = String.Concat(sb[slashIndex + 2], sb[slashIndex + 3]); 26 | char unicodeChar = Encoding.Unicode.GetChars(new byte[] { System.Convert.ToByte(hcode, 16), System.Convert.ToByte(lcode, 16) })[0]; 27 | sb.Remove(slashIndex, 6).Insert(slashIndex, unicodeChar); 28 | break; 29 | case 'n': sb.Remove(slashIndex, 2).Insert(slashIndex, '\n'); break; 30 | case 'r': sb.Remove(slashIndex, 2).Insert(slashIndex, '\r'); break; 31 | case 't': sb.Remove(slashIndex, 2).Insert(slashIndex, '\t'); break; 32 | case '\'': sb.Remove(slashIndex, 2).Insert(slashIndex, '\''); break; 33 | case '\\': sb.Remove(slashIndex, 2).Insert(slashIndex, '\\'); break; 34 | default: throw new ApplicationException("Unvalid escape sequence: \\" + escapeType); 35 | } 36 | 37 | startIndex = slashIndex + 1; 38 | 39 | } 40 | 41 | sb.Remove(0, 1); 42 | sb.Remove(sb.Length - 1, 1); 43 | 44 | return sb.ToString(); 45 | } 46 | 47 | public BinaryExpression And(LogicalExpression operand) 48 | { 49 | return new BinaryExpression(BinaryExpressionType.And, this, operand); 50 | } 51 | 52 | public BinaryExpression And(object operand) 53 | { 54 | return new BinaryExpression(BinaryExpressionType.And, this, new ValueExpression(operand)); 55 | } 56 | 57 | public BinaryExpression DividedBy(LogicalExpression operand) 58 | { 59 | return new BinaryExpression(BinaryExpressionType.Div, this, operand); 60 | } 61 | 62 | public BinaryExpression DividedBy(object operand) 63 | { 64 | return new BinaryExpression(BinaryExpressionType.Div, this, new ValueExpression(operand)); 65 | } 66 | 67 | public BinaryExpression EqualsTo(LogicalExpression operand) 68 | { 69 | return new BinaryExpression(BinaryExpressionType.Equal, this, operand); 70 | } 71 | 72 | public BinaryExpression EqualsTo(object operand) 73 | { 74 | return new BinaryExpression(BinaryExpressionType.Equal, this, new ValueExpression(operand)); 75 | } 76 | 77 | public BinaryExpression GreaterThan(LogicalExpression operand) 78 | { 79 | return new BinaryExpression(BinaryExpressionType.Greater, this, operand); 80 | } 81 | 82 | public BinaryExpression GreaterThan(object operand) 83 | { 84 | return new BinaryExpression(BinaryExpressionType.Greater, this, new ValueExpression(operand)); 85 | } 86 | 87 | public BinaryExpression GreaterOrEqualThan(LogicalExpression operand) 88 | { 89 | return new BinaryExpression(BinaryExpressionType.GreaterOrEqual, this, operand); 90 | } 91 | 92 | public BinaryExpression GreaterOrEqualThan(object operand) 93 | { 94 | return new BinaryExpression(BinaryExpressionType.GreaterOrEqual, this, new ValueExpression(operand)); 95 | } 96 | 97 | public BinaryExpression LesserThan(LogicalExpression operand) 98 | { 99 | return new BinaryExpression(BinaryExpressionType.Lesser, this, operand); 100 | } 101 | 102 | public BinaryExpression LesserThan(object operand) 103 | { 104 | return new BinaryExpression(BinaryExpressionType.Lesser, this, new ValueExpression(operand)); 105 | } 106 | 107 | public BinaryExpression LesserOrEqualThan(LogicalExpression operand) 108 | { 109 | return new BinaryExpression(BinaryExpressionType.LesserOrEqual, this, operand); 110 | } 111 | 112 | public BinaryExpression LesserOrEqualThan(object operand) 113 | { 114 | return new BinaryExpression(BinaryExpressionType.LesserOrEqual, this, new ValueExpression(operand)); 115 | } 116 | 117 | public BinaryExpression Minus(LogicalExpression operand) 118 | { 119 | return new BinaryExpression(BinaryExpressionType.Minus, this, operand); 120 | } 121 | 122 | public BinaryExpression Minus(object operand) 123 | { 124 | return new BinaryExpression(BinaryExpressionType.Minus, this, new ValueExpression(operand)); 125 | } 126 | 127 | public BinaryExpression Modulo(LogicalExpression operand) 128 | { 129 | return new BinaryExpression(BinaryExpressionType.Modulo, this, operand); 130 | } 131 | 132 | public BinaryExpression Modulo(object operand) 133 | { 134 | return new BinaryExpression(BinaryExpressionType.Modulo, this, new ValueExpression(operand)); 135 | } 136 | 137 | public BinaryExpression NotEqual(LogicalExpression operand) 138 | { 139 | return new BinaryExpression(BinaryExpressionType.NotEqual, this, operand); 140 | } 141 | 142 | public BinaryExpression NotEqual(object operand) 143 | { 144 | return new BinaryExpression(BinaryExpressionType.NotEqual, this, new ValueExpression(operand)); 145 | } 146 | 147 | public BinaryExpression Or(LogicalExpression operand) 148 | { 149 | return new BinaryExpression(BinaryExpressionType.Or, this, operand); 150 | } 151 | 152 | public BinaryExpression Or(object operand) 153 | { 154 | return new BinaryExpression(BinaryExpressionType.Or, this, new ValueExpression(operand)); 155 | } 156 | 157 | public BinaryExpression Plus(LogicalExpression operand) 158 | { 159 | return new BinaryExpression(BinaryExpressionType.Plus, this, operand); 160 | } 161 | 162 | public BinaryExpression Plus(object operand) 163 | { 164 | return new BinaryExpression(BinaryExpressionType.Plus, this, new ValueExpression(operand)); 165 | } 166 | 167 | public BinaryExpression Mult(LogicalExpression operand) 168 | { 169 | return new BinaryExpression(BinaryExpressionType.Times, this, operand); 170 | } 171 | 172 | public BinaryExpression Mult(object operand) 173 | { 174 | return new BinaryExpression(BinaryExpressionType.Times, this, new ValueExpression(operand)); 175 | } 176 | 177 | public BinaryExpression BitwiseOr(LogicalExpression operand) 178 | { 179 | return new BinaryExpression(BinaryExpressionType.BitwiseOr, this, operand); 180 | } 181 | 182 | public BinaryExpression BitwiseOr(object operand) 183 | { 184 | return new BinaryExpression(BinaryExpressionType.BitwiseOr, this, new ValueExpression(operand)); 185 | } 186 | 187 | public BinaryExpression BitwiseAnd(LogicalExpression operand) 188 | { 189 | return new BinaryExpression(BinaryExpressionType.BitwiseAnd, this, operand); 190 | } 191 | 192 | public BinaryExpression BitwiseAnd(object operand) 193 | { 194 | return new BinaryExpression(BinaryExpressionType.BitwiseAnd, this, new ValueExpression(operand)); 195 | } 196 | 197 | public BinaryExpression BitwiseXOr(LogicalExpression operand) 198 | { 199 | return new BinaryExpression(BinaryExpressionType.BitwiseXOr, this, operand); 200 | } 201 | 202 | public BinaryExpression BitwiseXOr(object operand) 203 | { 204 | return new BinaryExpression(BinaryExpressionType.BitwiseXOr, this, new ValueExpression(operand)); 205 | } 206 | 207 | public BinaryExpression LeftShift(LogicalExpression operand) 208 | { 209 | return new BinaryExpression(BinaryExpressionType.LeftShift, this, operand); 210 | } 211 | 212 | public BinaryExpression LeftShift(object operand) 213 | { 214 | return new BinaryExpression(BinaryExpressionType.LeftShift, this, new ValueExpression(operand)); 215 | } 216 | 217 | public BinaryExpression RightShift(LogicalExpression operand) 218 | { 219 | return new BinaryExpression(BinaryExpressionType.RightShift, this, operand); 220 | } 221 | 222 | public BinaryExpression RightShift(object operand) 223 | { 224 | return new BinaryExpression(BinaryExpressionType.RightShift, this, new ValueExpression(operand)); 225 | } 226 | 227 | public virtual async Task AcceptAsync(LogicalExpressionVisitor visitor) 228 | { 229 | await visitor.VisitAsync(this); 230 | } 231 | } 232 | } -------------------------------------------------------------------------------- /src/NCalcAsync/NCalcListener.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // ANTLR Version: 4.12.0 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | // Generated from NCalc.g by ANTLR 4.12.0 12 | 13 | // Unreachable code detected 14 | #pragma warning disable 0162 15 | // The variable '...' is assigned but its value is never used 16 | #pragma warning disable 0219 17 | // Missing XML comment for publicly visible type or member '...' 18 | #pragma warning disable 1591 19 | // Ambiguous reference in cref attribute 20 | #pragma warning disable 419 21 | 22 | 23 | using System.Globalization; 24 | using NCalcAsync.Domain; 25 | 26 | using Antlr4.Runtime.Misc; 27 | using IParseTreeListener = Antlr4.Runtime.Tree.IParseTreeListener; 28 | using IToken = Antlr4.Runtime.IToken; 29 | 30 | /// 31 | /// This interface defines a complete listener for a parse tree produced by 32 | /// . 33 | /// 34 | [System.CodeDom.Compiler.GeneratedCode("ANTLR", "4.12.0")] 35 | [System.CLSCompliant(false)] 36 | public interface INCalcListener : IParseTreeListener { 37 | /// 38 | /// Enter a parse tree produced by . 39 | /// 40 | /// The parse tree. 41 | void EnterNcalcExpression([NotNull] NCalcParser.NcalcExpressionContext context); 42 | /// 43 | /// Exit a parse tree produced by . 44 | /// 45 | /// The parse tree. 46 | void ExitNcalcExpression([NotNull] NCalcParser.NcalcExpressionContext context); 47 | /// 48 | /// Enter a parse tree produced by . 49 | /// 50 | /// The parse tree. 51 | void EnterLogicalExpression([NotNull] NCalcParser.LogicalExpressionContext context); 52 | /// 53 | /// Exit a parse tree produced by . 54 | /// 55 | /// The parse tree. 56 | void ExitLogicalExpression([NotNull] NCalcParser.LogicalExpressionContext context); 57 | /// 58 | /// Enter a parse tree produced by . 59 | /// 60 | /// The parse tree. 61 | void EnterConditionalExpression([NotNull] NCalcParser.ConditionalExpressionContext context); 62 | /// 63 | /// Exit a parse tree produced by . 64 | /// 65 | /// The parse tree. 66 | void ExitConditionalExpression([NotNull] NCalcParser.ConditionalExpressionContext context); 67 | /// 68 | /// Enter a parse tree produced by . 69 | /// 70 | /// The parse tree. 71 | void EnterBooleanAndExpression([NotNull] NCalcParser.BooleanAndExpressionContext context); 72 | /// 73 | /// Exit a parse tree produced by . 74 | /// 75 | /// The parse tree. 76 | void ExitBooleanAndExpression([NotNull] NCalcParser.BooleanAndExpressionContext context); 77 | /// 78 | /// Enter a parse tree produced by . 79 | /// 80 | /// The parse tree. 81 | void EnterBitwiseOrExpression([NotNull] NCalcParser.BitwiseOrExpressionContext context); 82 | /// 83 | /// Exit a parse tree produced by . 84 | /// 85 | /// The parse tree. 86 | void ExitBitwiseOrExpression([NotNull] NCalcParser.BitwiseOrExpressionContext context); 87 | /// 88 | /// Enter a parse tree produced by . 89 | /// 90 | /// The parse tree. 91 | void EnterBitwiseXOrExpression([NotNull] NCalcParser.BitwiseXOrExpressionContext context); 92 | /// 93 | /// Exit a parse tree produced by . 94 | /// 95 | /// The parse tree. 96 | void ExitBitwiseXOrExpression([NotNull] NCalcParser.BitwiseXOrExpressionContext context); 97 | /// 98 | /// Enter a parse tree produced by . 99 | /// 100 | /// The parse tree. 101 | void EnterBitwiseAndExpression([NotNull] NCalcParser.BitwiseAndExpressionContext context); 102 | /// 103 | /// Exit a parse tree produced by . 104 | /// 105 | /// The parse tree. 106 | void ExitBitwiseAndExpression([NotNull] NCalcParser.BitwiseAndExpressionContext context); 107 | /// 108 | /// Enter a parse tree produced by . 109 | /// 110 | /// The parse tree. 111 | void EnterEqualityExpression([NotNull] NCalcParser.EqualityExpressionContext context); 112 | /// 113 | /// Exit a parse tree produced by . 114 | /// 115 | /// The parse tree. 116 | void ExitEqualityExpression([NotNull] NCalcParser.EqualityExpressionContext context); 117 | /// 118 | /// Enter a parse tree produced by . 119 | /// 120 | /// The parse tree. 121 | void EnterRelationalExpression([NotNull] NCalcParser.RelationalExpressionContext context); 122 | /// 123 | /// Exit a parse tree produced by . 124 | /// 125 | /// The parse tree. 126 | void ExitRelationalExpression([NotNull] NCalcParser.RelationalExpressionContext context); 127 | /// 128 | /// Enter a parse tree produced by . 129 | /// 130 | /// The parse tree. 131 | void EnterShiftExpression([NotNull] NCalcParser.ShiftExpressionContext context); 132 | /// 133 | /// Exit a parse tree produced by . 134 | /// 135 | /// The parse tree. 136 | void ExitShiftExpression([NotNull] NCalcParser.ShiftExpressionContext context); 137 | /// 138 | /// Enter a parse tree produced by . 139 | /// 140 | /// The parse tree. 141 | void EnterAdditiveExpression([NotNull] NCalcParser.AdditiveExpressionContext context); 142 | /// 143 | /// Exit a parse tree produced by . 144 | /// 145 | /// The parse tree. 146 | void ExitAdditiveExpression([NotNull] NCalcParser.AdditiveExpressionContext context); 147 | /// 148 | /// Enter a parse tree produced by . 149 | /// 150 | /// The parse tree. 151 | void EnterMultiplicativeExpression([NotNull] NCalcParser.MultiplicativeExpressionContext context); 152 | /// 153 | /// Exit a parse tree produced by . 154 | /// 155 | /// The parse tree. 156 | void ExitMultiplicativeExpression([NotNull] NCalcParser.MultiplicativeExpressionContext context); 157 | /// 158 | /// Enter a parse tree produced by . 159 | /// 160 | /// The parse tree. 161 | void EnterUnaryExpression([NotNull] NCalcParser.UnaryExpressionContext context); 162 | /// 163 | /// Exit a parse tree produced by . 164 | /// 165 | /// The parse tree. 166 | void ExitUnaryExpression([NotNull] NCalcParser.UnaryExpressionContext context); 167 | /// 168 | /// Enter a parse tree produced by . 169 | /// 170 | /// The parse tree. 171 | void EnterExponentialExpression([NotNull] NCalcParser.ExponentialExpressionContext context); 172 | /// 173 | /// Exit a parse tree produced by . 174 | /// 175 | /// The parse tree. 176 | void ExitExponentialExpression([NotNull] NCalcParser.ExponentialExpressionContext context); 177 | /// 178 | /// Enter a parse tree produced by . 179 | /// 180 | /// The parse tree. 181 | void EnterPrimaryExpression([NotNull] NCalcParser.PrimaryExpressionContext context); 182 | /// 183 | /// Exit a parse tree produced by . 184 | /// 185 | /// The parse tree. 186 | void ExitPrimaryExpression([NotNull] NCalcParser.PrimaryExpressionContext context); 187 | /// 188 | /// Enter a parse tree produced by . 189 | /// 190 | /// The parse tree. 191 | void EnterValue([NotNull] NCalcParser.ValueContext context); 192 | /// 193 | /// Exit a parse tree produced by . 194 | /// 195 | /// The parse tree. 196 | void ExitValue([NotNull] NCalcParser.ValueContext context); 197 | /// 198 | /// Enter a parse tree produced by . 199 | /// 200 | /// The parse tree. 201 | void EnterIdentifier([NotNull] NCalcParser.IdentifierContext context); 202 | /// 203 | /// Exit a parse tree produced by . 204 | /// 205 | /// The parse tree. 206 | void ExitIdentifier([NotNull] NCalcParser.IdentifierContext context); 207 | /// 208 | /// Enter a parse tree produced by . 209 | /// 210 | /// The parse tree. 211 | void EnterExpressionList([NotNull] NCalcParser.ExpressionListContext context); 212 | /// 213 | /// Exit a parse tree produced by . 214 | /// 215 | /// The parse tree. 216 | void ExitExpressionList([NotNull] NCalcParser.ExpressionListContext context); 217 | /// 218 | /// Enter a parse tree produced by . 219 | /// 220 | /// The parse tree. 221 | void EnterArguments([NotNull] NCalcParser.ArgumentsContext context); 222 | /// 223 | /// Exit a parse tree produced by . 224 | /// 225 | /// The parse tree. 226 | void ExitArguments([NotNull] NCalcParser.ArgumentsContext context); 227 | } 228 | -------------------------------------------------------------------------------- /src/Grammar/NCalc.g: -------------------------------------------------------------------------------- 1 | grammar NCalc; 2 | 3 | options 4 | { 5 | language=CSharp3; 6 | } 7 | 8 | @header { 9 | using System.Globalization; 10 | using NCalcAsync.Domain; 11 | } 12 | 13 | @members { 14 | private const char BS = '\\'; 15 | private static NumberFormatInfo numberFormatInfo = new NumberFormatInfo(); 16 | 17 | private string extractString(string text) { 18 | 19 | StringBuilder sb = new StringBuilder(text); 20 | int startIndex = 1; // Skip initial quote 21 | int slashIndex = -1; 22 | 23 | while ((slashIndex = sb.ToString().IndexOf(BS, startIndex)) != -1) 24 | { 25 | char escapeType = sb[slashIndex + 1]; 26 | switch (escapeType) 27 | { 28 | case 'u': 29 | string hcode = String.Concat(sb[slashIndex+4], sb[slashIndex+5]); 30 | string lcode = String.Concat(sb[slashIndex+2], sb[slashIndex+3]); 31 | char unicodeChar = Encoding.Unicode.GetChars(new byte[] { System.Convert.ToByte(hcode, 16), System.Convert.ToByte(lcode, 16)} )[0]; 32 | sb.Remove(slashIndex, 6).Insert(slashIndex, unicodeChar); 33 | break; 34 | case 'n': sb.Remove(slashIndex, 2).Insert(slashIndex, '\n'); break; 35 | case 'r': sb.Remove(slashIndex, 2).Insert(slashIndex, '\r'); break; 36 | case 't': sb.Remove(slashIndex, 2).Insert(slashIndex, '\t'); break; 37 | case '\'': sb.Remove(slashIndex, 2).Insert(slashIndex, '\''); break; 38 | case '\\': sb.Remove(slashIndex, 2).Insert(slashIndex, '\\'); break; 39 | default: throw new RecognitionException(null, CharStreams.fromString("Invalid escape sequence: \\" + escapeType)); 40 | } 41 | 42 | startIndex = slashIndex + 1; 43 | 44 | } 45 | 46 | sb.Remove(0, 1); 47 | sb.Remove(sb.Length - 1, 1); 48 | 49 | return sb.ToString(); 50 | } 51 | 52 | } 53 | 54 | @init { 55 | numberFormatInfo.NumberDecimalSeparator = "."; 56 | } 57 | 58 | @lexer::namespace { NCalcAsync } 59 | @parser::namespace { NCalcAsync } 60 | 61 | ncalcExpression returns [LogicalExpression retValue] 62 | : logicalExpression EOF { $retValue = $logicalExpression.retValue; } 63 | ; 64 | 65 | logicalExpression returns [LogicalExpression retValue] 66 | : left=conditionalExpression { $retValue = $left.retValue; } ( '?' middle=conditionalExpression ':' right=conditionalExpression { $retValue = new TernaryExpression($left.retValue, $middle.retValue, $right.retValue); })? 67 | ; 68 | 69 | conditionalExpression returns [LogicalExpression retValue] 70 | @init { 71 | BinaryExpressionType type = BinaryExpressionType.Unknown; 72 | } 73 | : left=booleanAndExpression { $retValue = $left.retValue; } ( 74 | ('||' | OR) { type = BinaryExpressionType.Or; } 75 | right=conditionalExpression { $retValue = new BinaryExpression(type, $retValue, $right.retValue); } 76 | )* 77 | ; 78 | 79 | booleanAndExpression returns [LogicalExpression retValue] 80 | @init { 81 | BinaryExpressionType type = BinaryExpressionType.Unknown; 82 | } 83 | : left=bitwiseOrExpression { $retValue = $left.retValue; } ( 84 | ('&&' | AND) { type = BinaryExpressionType.And; } 85 | right=bitwiseOrExpression { $retValue = new BinaryExpression(type, $retValue, $right.retValue); } 86 | )* 87 | ; 88 | 89 | bitwiseOrExpression returns [LogicalExpression retValue] 90 | @init { 91 | BinaryExpressionType type = BinaryExpressionType.Unknown; 92 | } 93 | : left=bitwiseXOrExpression { $retValue = $left.retValue; } ( 94 | '|' { type = BinaryExpressionType.BitwiseOr; } 95 | right=bitwiseOrExpression { $retValue = new BinaryExpression(type, $retValue, $right.retValue); } 96 | )* 97 | ; 98 | 99 | bitwiseXOrExpression returns [LogicalExpression retValue] 100 | @init { 101 | BinaryExpressionType type = BinaryExpressionType.Unknown; 102 | } 103 | : left=bitwiseAndExpression { $retValue = $left.retValue; } ( 104 | '^' { type = BinaryExpressionType.BitwiseXOr; } 105 | right=bitwiseAndExpression { $retValue = new BinaryExpression(type, $retValue, $right.retValue); } 106 | )* 107 | ; 108 | 109 | bitwiseAndExpression returns [LogicalExpression retValue] 110 | @init { 111 | BinaryExpressionType type = BinaryExpressionType.Unknown; 112 | } 113 | : left=equalityExpression { $retValue = $left.retValue; } ( 114 | '&' { type = BinaryExpressionType.BitwiseAnd; } 115 | right=equalityExpression { $retValue = new BinaryExpression(type, $retValue, $right.retValue); } 116 | )* 117 | ; 118 | 119 | equalityExpression returns [LogicalExpression retValue] 120 | @init { 121 | BinaryExpressionType type = BinaryExpressionType.Unknown; 122 | } 123 | : left=relationalExpression { $retValue = $left.retValue; } ( 124 | ( ('==' | '=' ) { type = BinaryExpressionType.Equal; } 125 | | ('!=' | '<>' ) { type = BinaryExpressionType.NotEqual; } ) 126 | right=relationalExpression { $retValue = new BinaryExpression(type, $retValue, $right.retValue); } 127 | )* 128 | ; 129 | 130 | relationalExpression returns [LogicalExpression retValue] 131 | @init { 132 | BinaryExpressionType type = BinaryExpressionType.Unknown; 133 | } 134 | : left=shiftExpression { $retValue = $left.retValue; } ( 135 | ( '<' { type = BinaryExpressionType.Lesser; } 136 | | '<=' { type = BinaryExpressionType.LesserOrEqual; } 137 | | '>' { type = BinaryExpressionType.Greater; } 138 | | '>=' { type = BinaryExpressionType.GreaterOrEqual; } ) 139 | right=shiftExpression { $retValue = new BinaryExpression(type, $retValue, $right.retValue); } 140 | )* 141 | ; 142 | 143 | shiftExpression returns [LogicalExpression retValue] 144 | @init { 145 | BinaryExpressionType type = BinaryExpressionType.Unknown; 146 | } 147 | : left=additiveExpression { $retValue = $left.retValue; } ( 148 | ( '<<' { type = BinaryExpressionType.LeftShift; } 149 | | '>>' { type = BinaryExpressionType.RightShift; } ) 150 | right=additiveExpression { $retValue = new BinaryExpression(type, $retValue, $right.retValue); } 151 | )* 152 | ; 153 | 154 | additiveExpression returns [LogicalExpression retValue] 155 | @init { 156 | BinaryExpressionType type = BinaryExpressionType.Unknown; 157 | } 158 | : left=multiplicativeExpression { $retValue = $left.retValue; } ( 159 | ( '+' { type = BinaryExpressionType.Plus; } 160 | | '-' { type = BinaryExpressionType.Minus; } ) 161 | right=multiplicativeExpression { $retValue = new BinaryExpression(type, $retValue, $right.retValue); } 162 | )* 163 | ; 164 | 165 | multiplicativeExpression returns [LogicalExpression retValue] 166 | @init { 167 | BinaryExpressionType type = BinaryExpressionType.Unknown; 168 | } 169 | : left=unaryExpression { $retValue = $left.retValue; } ( 170 | ( '*' { type = BinaryExpressionType.Times; } 171 | | '/' { type = BinaryExpressionType.Div; } 172 | | '%' { type = BinaryExpressionType.Modulo; } ) 173 | right=unaryExpression { $retValue = new BinaryExpression(type, $retValue, $right.retValue); } 174 | )* 175 | ; 176 | 177 | unaryExpression returns [LogicalExpression retValue] 178 | : exponentialExpression { $retValue = $exponentialExpression.retValue; } 179 | | ('!' | NOT) exponentialExpression { $retValue = new UnaryExpression(UnaryExpressionType.Not, $exponentialExpression.retValue); } 180 | | ('~') exponentialExpression { $retValue = new UnaryExpression(UnaryExpressionType.BitwiseNot, $exponentialExpression.retValue); } 181 | | '-' exponentialExpression { $retValue = new UnaryExpression(UnaryExpressionType.Negate, $exponentialExpression.retValue); } 182 | | '+' exponentialExpression { $retValue = new UnaryExpression(UnaryExpressionType.Positive, $exponentialExpression.retValue); } 183 | ; 184 | 185 | exponentialExpression returns [LogicalExpression retValue] 186 | : left=primaryExpression { $retValue = $left.retValue; } ( 187 | '**' right=unaryExpression { $retValue = new BinaryExpression(BinaryExpressionType.Exponentiation, $retValue, $right.retValue); } 188 | )* 189 | ; 190 | 191 | primaryExpression returns [LogicalExpression retValue] 192 | : '(' logicalExpression ')' { $retValue = $logicalExpression.retValue; } 193 | | expr=value { $retValue = $expr.retValue; } 194 | | identifier {$retValue = (LogicalExpression) $identifier.retValue; } (arguments {$retValue = new Function($identifier.retValue, ($arguments.retValue).ToArray()); })? 195 | ; 196 | 197 | value returns [ValueExpression retValue] 198 | : INTEGER { try { $retValue = new ValueExpression(int.Parse($INTEGER.text)); } catch(System.OverflowException) { $retValue = new ValueExpression(long.Parse($INTEGER.text)); } } 199 | | FLOAT { $retValue = new ValueExpression(double.Parse($FLOAT.text, NumberStyles.Float, numberFormatInfo)); } 200 | | STRING { $retValue = new ValueExpression(extractString($STRING.text)); } 201 | | DATETIME { $retValue = new ValueExpression(DateTime.Parse($DATETIME.text.Substring(1, $DATETIME.text.Length-2))); } 202 | | TRUE { $retValue = new ValueExpression(true); } 203 | | FALSE { $retValue = new ValueExpression(false); } 204 | ; 205 | 206 | identifier returns[Identifier retValue] 207 | : ID { $retValue = new Identifier($ID.text); } 208 | | NAME { $retValue = new Identifier($NAME.text.Substring(1, $NAME.text.Length-2)); } 209 | ; 210 | 211 | expressionList returns [List retValue] 212 | @init { 213 | List expressions = new List(); 214 | } 215 | : first=logicalExpression {expressions.Add($first.retValue);} ( ',' follow=logicalExpression {expressions.Add($follow.retValue);})* 216 | { $retValue = expressions; } 217 | ; 218 | 219 | arguments returns [List retValue] 220 | @init { 221 | $retValue = new List(); 222 | } 223 | : '(' ( expressionList {$retValue = $expressionList.retValue;} )? ')' 224 | ; 225 | 226 | TRUE: T R U E ; 227 | FALSE: F A L S E ; 228 | AND: A N D ; 229 | OR: O R ; 230 | NOT: N O T ; 231 | 232 | ID 233 | : LETTER (LETTER | DIGIT)* 234 | ; 235 | 236 | INTEGER 237 | : DIGIT+ 238 | ; 239 | 240 | FLOAT 241 | : DIGIT* '.' DIGIT+ EXPONENT? 242 | | DIGIT+ '.' DIGIT* EXPONENT? 243 | | DIGIT+ EXPONENT 244 | ; 245 | 246 | STRING 247 | : '\'' ( EscapeSequence | ( ~('\u0000'..'\u001f' | '\\' | '\'' ) ).*? )* '\'' 248 | ; 249 | 250 | DATETIME 251 | : '#' ( ~('#')*) '#' 252 | ; 253 | 254 | NAME : '[' ( ~(']')*) ']' 255 | ; 256 | 257 | EXPONENT 258 | : ('E'|'e') ('+'|'-')? DIGIT+ 259 | ; 260 | 261 | fragment LETTER 262 | : 'a'..'z' 263 | | 'A'..'Z' 264 | | '_' 265 | ; 266 | 267 | fragment DIGIT 268 | : '0'..'9' 269 | ; 270 | 271 | fragment EscapeSequence 272 | : '\\' 273 | ( 274 | 'n' 275 | | 'r' 276 | | 't' 277 | | '\'' 278 | | '\\' 279 | | UnicodeEscape 280 | ) 281 | ; 282 | 283 | fragment HexDigit 284 | : ('0'..'9'|'a'..'f'|'A'..'F') ; 285 | 286 | 287 | fragment UnicodeEscape 288 | : 'u' HexDigit HexDigit HexDigit HexDigit 289 | ; 290 | 291 | /* Ignore white spaces */ 292 | WS : (' '|'\r'|'\t'|'\u000C'|'\n') -> skip; 293 | 294 | /* Allow case-insensitive operators by constructing them out of fragments. 295 | * Solution adapted from https://stackoverflow.com/a/22160240 296 | */ 297 | fragment A: 'a' | 'A'; 298 | fragment B: 'b' | 'B'; 299 | fragment C: 'c' | 'C'; 300 | fragment D: 'd' | 'D'; 301 | fragment E: 'e' | 'E'; 302 | fragment F: 'f' | 'F'; 303 | fragment G: 'g' | 'G'; 304 | fragment H: 'h' | 'H'; 305 | fragment I: 'i' | 'I'; 306 | fragment J: 'j' | 'J'; 307 | fragment K: 'k' | 'K'; 308 | fragment L: 'l' | 'L'; 309 | fragment M: 'm' | 'M'; 310 | fragment N: 'n' | 'N'; 311 | fragment O: 'o' | 'O'; 312 | fragment P: 'p' | 'P'; 313 | fragment Q: 'q' | 'Q'; 314 | fragment R: 'r' | 'R'; 315 | fragment S: 's' | 'S'; 316 | fragment T: 't' | 'T'; 317 | fragment U: 'u' | 'U'; 318 | fragment V: 'v' | 'V'; 319 | fragment W: 'w' | 'W'; 320 | fragment X: 'x' | 'X'; 321 | fragment Y: 'y' | 'Y'; 322 | fragment Z: 'z' | 'Z'; -------------------------------------------------------------------------------- /src/NCalcAsync/Expression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Antlr4.Runtime; 10 | using NCalcAsync.Domain; 11 | 12 | namespace NCalcAsync 13 | { 14 | public class Expression 15 | { 16 | public NumberConversionTypePreference NumberConversionTypePreference { get; set; } 17 | public EvaluateOptions Options { get; set; } 18 | 19 | /// 20 | /// Textual representation of the expression to evaluate. 21 | /// 22 | protected string OriginalExpression; 23 | 24 | public EvaluateParameterAsyncHandler EvaluateParameterAsync { get; set; } 25 | public EvaluateFunctionAsyncHandler EvaluateFunctionAsync { get; set; } 26 | 27 | public Expression(string expression, EvaluateOptions options = EvaluateOptions.None, NumberConversionTypePreference numberConversionTypePreference = NumberConversionTypePreference.Decimal) 28 | { 29 | if (String.IsNullOrEmpty(expression)) 30 | throw new 31 | ArgumentException("Expression can't be empty", "expression"); 32 | 33 | OriginalExpression = expression; 34 | Options = options; 35 | NumberConversionTypePreference = numberConversionTypePreference; 36 | } 37 | 38 | public Expression(LogicalExpression expression, EvaluateOptions options = EvaluateOptions.None, NumberConversionTypePreference numberConversionTypePreference = NumberConversionTypePreference.Decimal) 39 | { 40 | if (expression == null) 41 | throw new 42 | ArgumentException("Expression can't be null", "expression"); 43 | 44 | ParsedExpression = expression; 45 | Options = options; 46 | NumberConversionTypePreference = numberConversionTypePreference; 47 | } 48 | 49 | #region Cache management 50 | private static bool _cacheEnabled = true; 51 | private static Dictionary _compiledExpressions = new Dictionary(); 52 | private static readonly ReaderWriterLock Rwl = new ReaderWriterLock(); 53 | 54 | public static bool CacheEnabled 55 | { 56 | get { return _cacheEnabled; } 57 | set 58 | { 59 | _cacheEnabled = value; 60 | 61 | if (!CacheEnabled) 62 | { 63 | // Clears cache 64 | _compiledExpressions = new Dictionary(); 65 | } 66 | } 67 | } 68 | 69 | /// 70 | /// Removed unused entries from cached compiled expression 71 | /// 72 | private static void CleanCache() 73 | { 74 | var keysToRemove = new List(); 75 | 76 | try 77 | { 78 | Rwl.AcquireWriterLock(Timeout.Infinite); 79 | foreach (var de in _compiledExpressions) 80 | { 81 | if (!de.Value.IsAlive) 82 | { 83 | keysToRemove.Add(de.Key); 84 | } 85 | } 86 | 87 | 88 | foreach (string key in keysToRemove) 89 | { 90 | _compiledExpressions.Remove(key); 91 | Trace.TraceInformation("Cache entry released: " + key); 92 | } 93 | } 94 | finally 95 | { 96 | Rwl.ReleaseWriterLock(); 97 | } 98 | } 99 | 100 | #endregion 101 | 102 | public static LogicalExpression Compile(string expression, bool nocache) 103 | { 104 | LogicalExpression logicalExpression = null; 105 | 106 | if (_cacheEnabled && !nocache) 107 | { 108 | try 109 | { 110 | Rwl.AcquireReaderLock(Timeout.Infinite); 111 | 112 | if (_compiledExpressions.ContainsKey(expression)) 113 | { 114 | Trace.TraceInformation("Expression retrieved from cache: " + expression); 115 | var wr = _compiledExpressions[expression]; 116 | logicalExpression = wr.Target as LogicalExpression; 117 | 118 | if (wr.IsAlive && logicalExpression != null) 119 | { 120 | return logicalExpression; 121 | } 122 | } 123 | } 124 | finally 125 | { 126 | Rwl.ReleaseReaderLock(); 127 | } 128 | } 129 | 130 | if (logicalExpression == null) 131 | { 132 | var lexer = new NCalcLexer(new AntlrInputStream(expression)); 133 | var errorListenerLexer = new ErrorListenerLexer(); 134 | lexer.AddErrorListener(errorListenerLexer); 135 | 136 | var parser = new NCalcParser(new CommonTokenStream(lexer)); 137 | var errorListenerParser = new ErrorListenerParser(); 138 | parser.AddErrorListener(errorListenerParser); 139 | 140 | try 141 | { 142 | logicalExpression = parser.ncalcExpression().retValue; 143 | } 144 | catch(Exception ex) 145 | { 146 | StringBuilder message = new StringBuilder(ex.Message); 147 | if (errorListenerLexer.Errors.Any()) 148 | { 149 | message.AppendLine(); 150 | message.AppendLine(String.Join(Environment.NewLine, errorListenerLexer.Errors.ToArray())); 151 | } 152 | if (errorListenerParser.Errors.Any()) 153 | { 154 | message.AppendLine(); 155 | message.AppendLine(String.Join(Environment.NewLine, errorListenerParser.Errors.ToArray())); 156 | } 157 | 158 | throw new EvaluationException(message.ToString()); 159 | } 160 | if (errorListenerLexer.Errors.Any()) 161 | { 162 | throw new EvaluationException(String.Join(Environment.NewLine, errorListenerLexer.Errors.ToArray())); 163 | } 164 | if (errorListenerParser.Errors.Any()) 165 | { 166 | throw new EvaluationException(String.Join(Environment.NewLine, errorListenerParser.Errors.ToArray())); 167 | } 168 | 169 | if (_cacheEnabled && !nocache) 170 | { 171 | try 172 | { 173 | Rwl.AcquireWriterLock(Timeout.Infinite); 174 | _compiledExpressions[expression] = new WeakReference(logicalExpression); 175 | } 176 | finally 177 | { 178 | Rwl.ReleaseWriterLock(); 179 | } 180 | 181 | CleanCache(); 182 | 183 | Trace.TraceInformation("Expression added to cache: " + expression); 184 | } 185 | } 186 | 187 | return logicalExpression; 188 | } 189 | 190 | /// 191 | /// Pre-compiles the expression in order to check syntax errors. 192 | /// If errors are detected, the Error property contains the message. 193 | /// 194 | /// True if the expression syntax is correct, otherwiser False 195 | public bool HasErrors() 196 | { 197 | try 198 | { 199 | if (ParsedExpression == null) 200 | { 201 | ParsedExpression = Compile(OriginalExpression, (Options & EvaluateOptions.NoCache) == EvaluateOptions.NoCache); 202 | } 203 | 204 | // In case HasErrors() is called multiple times for the same expression 205 | return ParsedExpression != null && Error != null; 206 | } 207 | catch (Exception e) 208 | { 209 | Error = e.Message; 210 | return true; 211 | } 212 | } 213 | 214 | public string Error { get; private set; } 215 | 216 | public LogicalExpression ParsedExpression { get; private set; } 217 | 218 | protected Dictionary ParameterEnumerators; 219 | protected Dictionary ParametersBackup; 220 | 221 | /// 222 | /// Evaluate the expression asynchronously. 223 | /// 224 | /// A task that resolves to the result of the expression. 225 | public async Task EvaluateAsync() 226 | { 227 | return await EvaluateAsync(EvaluateParameterAsync, EvaluateFunctionAsync); 228 | } 229 | 230 | /// 231 | /// Evaluate the expression asynchronously. 232 | /// 233 | /// Override the value of 234 | /// Override the value of 235 | /// A task that resolves to the result of the expression. 236 | public async Task EvaluateAsync(EvaluateParameterAsyncHandler evaluateParameterAsync, EvaluateFunctionAsyncHandler evaluateFunctionAsync) 237 | { 238 | if (HasErrors()) 239 | { 240 | throw new EvaluationException(Error); 241 | } 242 | 243 | if (ParsedExpression == null) 244 | { 245 | ParsedExpression = Compile(OriginalExpression, (Options & EvaluateOptions.NoCache) == EvaluateOptions.NoCache); 246 | } 247 | 248 | var visitor = new EvaluationVisitor(Options, evaluateParameterAsync, evaluateFunctionAsync, NumberConversionTypePreference) 249 | { 250 | Parameters = Parameters 251 | }; 252 | 253 | // if array evaluation, execute the same expression multiple times 254 | if ((Options & EvaluateOptions.IterateParameters) == EvaluateOptions.IterateParameters) 255 | { 256 | int size = -1; 257 | ParametersBackup = new Dictionary(); 258 | foreach (string key in Parameters.Keys) 259 | { 260 | ParametersBackup.Add(key, Parameters[key]); 261 | } 262 | 263 | ParameterEnumerators = new Dictionary(); 264 | 265 | foreach (object parameter in Parameters.Values) 266 | { 267 | if (parameter is IEnumerable) 268 | { 269 | int localsize = 0; 270 | foreach (object o in (IEnumerable)parameter) 271 | { 272 | localsize++; 273 | } 274 | 275 | if (size == -1) 276 | { 277 | size = localsize; 278 | } 279 | else if (localsize != size) 280 | { 281 | throw new EvaluationException("When IterateParameters option is used, IEnumerable parameters must have the same number of items"); 282 | } 283 | } 284 | } 285 | 286 | foreach (string key in Parameters.Keys) 287 | { 288 | var parameter = Parameters[key] as IEnumerable; 289 | if (parameter != null) 290 | { 291 | ParameterEnumerators.Add(key, parameter.GetEnumerator()); 292 | } 293 | } 294 | 295 | var results = new List(); 296 | for (int i = 0; i < size; i++) 297 | { 298 | foreach (string key in ParameterEnumerators.Keys) 299 | { 300 | IEnumerator enumerator = ParameterEnumerators[key]; 301 | enumerator.MoveNext(); 302 | Parameters[key] = enumerator.Current; 303 | } 304 | 305 | await ParsedExpression.AcceptAsync(visitor); 306 | results.Add(visitor.Result); 307 | } 308 | 309 | return results; 310 | } 311 | 312 | await ParsedExpression.AcceptAsync(visitor); 313 | return visitor.Result; 314 | } 315 | 316 | private Dictionary _parameters; 317 | 318 | public Dictionary Parameters 319 | { 320 | get { return _parameters ?? (_parameters = new Dictionary()); } 321 | set { _parameters = value; } 322 | } 323 | } 324 | } -------------------------------------------------------------------------------- /src/NCalcAsync/NCalcBaseListener.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // ANTLR Version: 4.12.0 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | // Generated from NCalc.g by ANTLR 4.12.0 12 | 13 | // Unreachable code detected 14 | #pragma warning disable 0162 15 | // The variable '...' is assigned but its value is never used 16 | #pragma warning disable 0219 17 | // Missing XML comment for publicly visible type or member '...' 18 | #pragma warning disable 1591 19 | // Ambiguous reference in cref attribute 20 | #pragma warning disable 419 21 | 22 | 23 | using System.Globalization; 24 | using NCalcAsync.Domain; 25 | 26 | 27 | using Antlr4.Runtime.Misc; 28 | using IErrorNode = Antlr4.Runtime.Tree.IErrorNode; 29 | using ITerminalNode = Antlr4.Runtime.Tree.ITerminalNode; 30 | using IToken = Antlr4.Runtime.IToken; 31 | using ParserRuleContext = Antlr4.Runtime.ParserRuleContext; 32 | 33 | /// 34 | /// This class provides an empty implementation of , 35 | /// which can be extended to create a listener which only needs to handle a subset 36 | /// of the available methods. 37 | /// 38 | [System.CodeDom.Compiler.GeneratedCode("ANTLR", "4.12.0")] 39 | [System.Diagnostics.DebuggerNonUserCode] 40 | [System.CLSCompliant(false)] 41 | public partial class NCalcBaseListener : INCalcListener { 42 | /// 43 | /// Enter a parse tree produced by . 44 | /// The default implementation does nothing. 45 | /// 46 | /// The parse tree. 47 | public virtual void EnterNcalcExpression([NotNull] NCalcParser.NcalcExpressionContext context) { } 48 | /// 49 | /// Exit a parse tree produced by . 50 | /// The default implementation does nothing. 51 | /// 52 | /// The parse tree. 53 | public virtual void ExitNcalcExpression([NotNull] NCalcParser.NcalcExpressionContext context) { } 54 | /// 55 | /// Enter a parse tree produced by . 56 | /// The default implementation does nothing. 57 | /// 58 | /// The parse tree. 59 | public virtual void EnterLogicalExpression([NotNull] NCalcParser.LogicalExpressionContext context) { } 60 | /// 61 | /// Exit a parse tree produced by . 62 | /// The default implementation does nothing. 63 | /// 64 | /// The parse tree. 65 | public virtual void ExitLogicalExpression([NotNull] NCalcParser.LogicalExpressionContext context) { } 66 | /// 67 | /// Enter a parse tree produced by . 68 | /// The default implementation does nothing. 69 | /// 70 | /// The parse tree. 71 | public virtual void EnterConditionalExpression([NotNull] NCalcParser.ConditionalExpressionContext context) { } 72 | /// 73 | /// Exit a parse tree produced by . 74 | /// The default implementation does nothing. 75 | /// 76 | /// The parse tree. 77 | public virtual void ExitConditionalExpression([NotNull] NCalcParser.ConditionalExpressionContext context) { } 78 | /// 79 | /// Enter a parse tree produced by . 80 | /// The default implementation does nothing. 81 | /// 82 | /// The parse tree. 83 | public virtual void EnterBooleanAndExpression([NotNull] NCalcParser.BooleanAndExpressionContext context) { } 84 | /// 85 | /// Exit a parse tree produced by . 86 | /// The default implementation does nothing. 87 | /// 88 | /// The parse tree. 89 | public virtual void ExitBooleanAndExpression([NotNull] NCalcParser.BooleanAndExpressionContext context) { } 90 | /// 91 | /// Enter a parse tree produced by . 92 | /// The default implementation does nothing. 93 | /// 94 | /// The parse tree. 95 | public virtual void EnterBitwiseOrExpression([NotNull] NCalcParser.BitwiseOrExpressionContext context) { } 96 | /// 97 | /// Exit a parse tree produced by . 98 | /// The default implementation does nothing. 99 | /// 100 | /// The parse tree. 101 | public virtual void ExitBitwiseOrExpression([NotNull] NCalcParser.BitwiseOrExpressionContext context) { } 102 | /// 103 | /// Enter a parse tree produced by . 104 | /// The default implementation does nothing. 105 | /// 106 | /// The parse tree. 107 | public virtual void EnterBitwiseXOrExpression([NotNull] NCalcParser.BitwiseXOrExpressionContext context) { } 108 | /// 109 | /// Exit a parse tree produced by . 110 | /// The default implementation does nothing. 111 | /// 112 | /// The parse tree. 113 | public virtual void ExitBitwiseXOrExpression([NotNull] NCalcParser.BitwiseXOrExpressionContext context) { } 114 | /// 115 | /// Enter a parse tree produced by . 116 | /// The default implementation does nothing. 117 | /// 118 | /// The parse tree. 119 | public virtual void EnterBitwiseAndExpression([NotNull] NCalcParser.BitwiseAndExpressionContext context) { } 120 | /// 121 | /// Exit a parse tree produced by . 122 | /// The default implementation does nothing. 123 | /// 124 | /// The parse tree. 125 | public virtual void ExitBitwiseAndExpression([NotNull] NCalcParser.BitwiseAndExpressionContext context) { } 126 | /// 127 | /// Enter a parse tree produced by . 128 | /// The default implementation does nothing. 129 | /// 130 | /// The parse tree. 131 | public virtual void EnterEqualityExpression([NotNull] NCalcParser.EqualityExpressionContext context) { } 132 | /// 133 | /// Exit a parse tree produced by . 134 | /// The default implementation does nothing. 135 | /// 136 | /// The parse tree. 137 | public virtual void ExitEqualityExpression([NotNull] NCalcParser.EqualityExpressionContext context) { } 138 | /// 139 | /// Enter a parse tree produced by . 140 | /// The default implementation does nothing. 141 | /// 142 | /// The parse tree. 143 | public virtual void EnterRelationalExpression([NotNull] NCalcParser.RelationalExpressionContext context) { } 144 | /// 145 | /// Exit a parse tree produced by . 146 | /// The default implementation does nothing. 147 | /// 148 | /// The parse tree. 149 | public virtual void ExitRelationalExpression([NotNull] NCalcParser.RelationalExpressionContext context) { } 150 | /// 151 | /// Enter a parse tree produced by . 152 | /// The default implementation does nothing. 153 | /// 154 | /// The parse tree. 155 | public virtual void EnterShiftExpression([NotNull] NCalcParser.ShiftExpressionContext context) { } 156 | /// 157 | /// Exit a parse tree produced by . 158 | /// The default implementation does nothing. 159 | /// 160 | /// The parse tree. 161 | public virtual void ExitShiftExpression([NotNull] NCalcParser.ShiftExpressionContext context) { } 162 | /// 163 | /// Enter a parse tree produced by . 164 | /// The default implementation does nothing. 165 | /// 166 | /// The parse tree. 167 | public virtual void EnterAdditiveExpression([NotNull] NCalcParser.AdditiveExpressionContext context) { } 168 | /// 169 | /// Exit a parse tree produced by . 170 | /// The default implementation does nothing. 171 | /// 172 | /// The parse tree. 173 | public virtual void ExitAdditiveExpression([NotNull] NCalcParser.AdditiveExpressionContext context) { } 174 | /// 175 | /// Enter a parse tree produced by . 176 | /// The default implementation does nothing. 177 | /// 178 | /// The parse tree. 179 | public virtual void EnterMultiplicativeExpression([NotNull] NCalcParser.MultiplicativeExpressionContext context) { } 180 | /// 181 | /// Exit a parse tree produced by . 182 | /// The default implementation does nothing. 183 | /// 184 | /// The parse tree. 185 | public virtual void ExitMultiplicativeExpression([NotNull] NCalcParser.MultiplicativeExpressionContext context) { } 186 | /// 187 | /// Enter a parse tree produced by . 188 | /// The default implementation does nothing. 189 | /// 190 | /// The parse tree. 191 | public virtual void EnterUnaryExpression([NotNull] NCalcParser.UnaryExpressionContext context) { } 192 | /// 193 | /// Exit a parse tree produced by . 194 | /// The default implementation does nothing. 195 | /// 196 | /// The parse tree. 197 | public virtual void ExitUnaryExpression([NotNull] NCalcParser.UnaryExpressionContext context) { } 198 | /// 199 | /// Enter a parse tree produced by . 200 | /// The default implementation does nothing. 201 | /// 202 | /// The parse tree. 203 | public virtual void EnterExponentialExpression([NotNull] NCalcParser.ExponentialExpressionContext context) { } 204 | /// 205 | /// Exit a parse tree produced by . 206 | /// The default implementation does nothing. 207 | /// 208 | /// The parse tree. 209 | public virtual void ExitExponentialExpression([NotNull] NCalcParser.ExponentialExpressionContext context) { } 210 | /// 211 | /// Enter a parse tree produced by . 212 | /// The default implementation does nothing. 213 | /// 214 | /// The parse tree. 215 | public virtual void EnterPrimaryExpression([NotNull] NCalcParser.PrimaryExpressionContext context) { } 216 | /// 217 | /// Exit a parse tree produced by . 218 | /// The default implementation does nothing. 219 | /// 220 | /// The parse tree. 221 | public virtual void ExitPrimaryExpression([NotNull] NCalcParser.PrimaryExpressionContext context) { } 222 | /// 223 | /// Enter a parse tree produced by . 224 | /// The default implementation does nothing. 225 | /// 226 | /// The parse tree. 227 | public virtual void EnterValue([NotNull] NCalcParser.ValueContext context) { } 228 | /// 229 | /// Exit a parse tree produced by . 230 | /// The default implementation does nothing. 231 | /// 232 | /// The parse tree. 233 | public virtual void ExitValue([NotNull] NCalcParser.ValueContext context) { } 234 | /// 235 | /// Enter a parse tree produced by . 236 | /// The default implementation does nothing. 237 | /// 238 | /// The parse tree. 239 | public virtual void EnterIdentifier([NotNull] NCalcParser.IdentifierContext context) { } 240 | /// 241 | /// Exit a parse tree produced by . 242 | /// The default implementation does nothing. 243 | /// 244 | /// The parse tree. 245 | public virtual void ExitIdentifier([NotNull] NCalcParser.IdentifierContext context) { } 246 | /// 247 | /// Enter a parse tree produced by . 248 | /// The default implementation does nothing. 249 | /// 250 | /// The parse tree. 251 | public virtual void EnterExpressionList([NotNull] NCalcParser.ExpressionListContext context) { } 252 | /// 253 | /// Exit a parse tree produced by . 254 | /// The default implementation does nothing. 255 | /// 256 | /// The parse tree. 257 | public virtual void ExitExpressionList([NotNull] NCalcParser.ExpressionListContext context) { } 258 | /// 259 | /// Enter a parse tree produced by . 260 | /// The default implementation does nothing. 261 | /// 262 | /// The parse tree. 263 | public virtual void EnterArguments([NotNull] NCalcParser.ArgumentsContext context) { } 264 | /// 265 | /// Exit a parse tree produced by . 266 | /// The default implementation does nothing. 267 | /// 268 | /// The parse tree. 269 | public virtual void ExitArguments([NotNull] NCalcParser.ArgumentsContext context) { } 270 | 271 | /// 272 | /// The default implementation does nothing. 273 | public virtual void EnterEveryRule([NotNull] ParserRuleContext context) { } 274 | /// 275 | /// The default implementation does nothing. 276 | public virtual void ExitEveryRule([NotNull] ParserRuleContext context) { } 277 | /// 278 | /// The default implementation does nothing. 279 | public virtual void VisitTerminal([NotNull] ITerminalNode node) { } 280 | /// 281 | /// The default implementation does nothing. 282 | public virtual void VisitErrorNode([NotNull] IErrorNode node) { } 283 | } 284 | -------------------------------------------------------------------------------- /src/NCalcAsync/NCalcLexer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // ANTLR Version: 4.12.0 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | // Generated from NCalc.g by ANTLR 4.12.0 12 | 13 | // Unreachable code detected 14 | #pragma warning disable 0162 15 | // The variable '...' is assigned but its value is never used 16 | #pragma warning disable 0219 17 | // Missing XML comment for publicly visible type or member '...' 18 | #pragma warning disable 1591 19 | // Ambiguous reference in cref attribute 20 | #pragma warning disable 419 21 | 22 | 23 | using System.Globalization; 24 | using NCalcAsync.Domain; 25 | 26 | using System; 27 | using System.IO; 28 | using System.Text; 29 | using Antlr4.Runtime; 30 | using Antlr4.Runtime.Atn; 31 | using Antlr4.Runtime.Misc; 32 | using DFA = Antlr4.Runtime.Dfa.DFA; 33 | 34 | [System.CodeDom.Compiler.GeneratedCode("ANTLR", "4.12.0")] 35 | [System.CLSCompliant(false)] 36 | public partial class NCalcLexer : Lexer { 37 | protected static DFA[] decisionToDFA; 38 | protected static PredictionContextCache sharedContextCache = new PredictionContextCache(); 39 | public const int 40 | T__0=1, T__1=2, T__2=3, T__3=4, T__4=5, T__5=6, T__6=7, T__7=8, T__8=9, 41 | T__9=10, T__10=11, T__11=12, T__12=13, T__13=14, T__14=15, T__15=16, T__16=17, 42 | T__17=18, T__18=19, T__19=20, T__20=21, T__21=22, T__22=23, T__23=24, 43 | T__24=25, T__25=26, T__26=27, T__27=28, TRUE=29, FALSE=30, AND=31, OR=32, 44 | NOT=33, ID=34, INTEGER=35, FLOAT=36, STRING=37, DATETIME=38, NAME=39, 45 | EXPONENT=40, WS=41; 46 | public static string[] channelNames = { 47 | "DEFAULT_TOKEN_CHANNEL", "HIDDEN" 48 | }; 49 | 50 | public static string[] modeNames = { 51 | "DEFAULT_MODE" 52 | }; 53 | 54 | public static readonly string[] ruleNames = { 55 | "T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6", "T__7", "T__8", 56 | "T__9", "T__10", "T__11", "T__12", "T__13", "T__14", "T__15", "T__16", 57 | "T__17", "T__18", "T__19", "T__20", "T__21", "T__22", "T__23", "T__24", 58 | "T__25", "T__26", "T__27", "TRUE", "FALSE", "AND", "OR", "NOT", "ID", 59 | "INTEGER", "FLOAT", "STRING", "DATETIME", "NAME", "EXPONENT", "LETTER", 60 | "DIGIT", "EscapeSequence", "HexDigit", "UnicodeEscape", "WS", "A", "B", 61 | "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", 62 | "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" 63 | }; 64 | 65 | 66 | private const char BS = '\\'; 67 | private static NumberFormatInfo numberFormatInfo = new NumberFormatInfo(); 68 | 69 | private string extractString(string text) { 70 | 71 | StringBuilder sb = new StringBuilder(text); 72 | int startIndex = 1; // Skip initial quote 73 | int slashIndex = -1; 74 | 75 | while ((slashIndex = sb.ToString().IndexOf(BS, startIndex)) != -1) 76 | { 77 | char escapeType = sb[slashIndex + 1]; 78 | switch (escapeType) 79 | { 80 | case 'u': 81 | string hcode = String.Concat(sb[slashIndex+4], sb[slashIndex+5]); 82 | string lcode = String.Concat(sb[slashIndex+2], sb[slashIndex+3]); 83 | char unicodeChar = Encoding.Unicode.GetChars(new byte[] { System.Convert.ToByte(hcode, 16), System.Convert.ToByte(lcode, 16)} )[0]; 84 | sb.Remove(slashIndex, 6).Insert(slashIndex, unicodeChar); 85 | break; 86 | case 'n': sb.Remove(slashIndex, 2).Insert(slashIndex, '\n'); break; 87 | case 'r': sb.Remove(slashIndex, 2).Insert(slashIndex, '\r'); break; 88 | case 't': sb.Remove(slashIndex, 2).Insert(slashIndex, '\t'); break; 89 | case '\'': sb.Remove(slashIndex, 2).Insert(slashIndex, '\''); break; 90 | case '\\': sb.Remove(slashIndex, 2).Insert(slashIndex, '\\'); break; 91 | default: throw new RecognitionException(null, CharStreams.fromString("Invalid escape sequence: \\" + escapeType)); 92 | } 93 | 94 | startIndex = slashIndex + 1; 95 | 96 | } 97 | 98 | sb.Remove(0, 1); 99 | sb.Remove(sb.Length - 1, 1); 100 | 101 | return sb.ToString(); 102 | } 103 | 104 | 105 | 106 | public NCalcLexer(ICharStream input) 107 | : this(input, Console.Out, Console.Error) { } 108 | 109 | public NCalcLexer(ICharStream input, TextWriter output, TextWriter errorOutput) 110 | : base(input, output, errorOutput) 111 | { 112 | Interpreter = new LexerATNSimulator(this, _ATN, decisionToDFA, sharedContextCache); 113 | } 114 | 115 | private static readonly string[] _LiteralNames = { 116 | null, "'?'", "':'", "'||'", "'&&'", "'|'", "'^'", "'&'", "'=='", "'='", 117 | "'!='", "'<>'", "'<'", "'<='", "'>'", "'>='", "'<<'", "'>>'", "'+'", "'-'", 118 | "'*'", "'/'", "'%'", "'!'", "'~'", "'**'", "'('", "')'", "','" 119 | }; 120 | private static readonly string[] _SymbolicNames = { 121 | null, null, null, null, null, null, null, null, null, null, null, null, 122 | null, null, null, null, null, null, null, null, null, null, null, null, 123 | null, null, null, null, null, "TRUE", "FALSE", "AND", "OR", "NOT", "ID", 124 | "INTEGER", "FLOAT", "STRING", "DATETIME", "NAME", "EXPONENT", "WS" 125 | }; 126 | public static readonly IVocabulary DefaultVocabulary = new Vocabulary(_LiteralNames, _SymbolicNames); 127 | 128 | [NotNull] 129 | public override IVocabulary Vocabulary 130 | { 131 | get 132 | { 133 | return DefaultVocabulary; 134 | } 135 | } 136 | 137 | public override string GrammarFileName { get { return "NCalc.g"; } } 138 | 139 | public override string[] RuleNames { get { return ruleNames; } } 140 | 141 | public override string[] ChannelNames { get { return channelNames; } } 142 | 143 | public override string[] ModeNames { get { return modeNames; } } 144 | 145 | public override int[] SerializedAtn { get { return _serializedATN; } } 146 | 147 | static NCalcLexer() { 148 | decisionToDFA = new DFA[_ATN.NumberOfDecisions]; 149 | for (int i = 0; i < _ATN.NumberOfDecisions; i++) { 150 | decisionToDFA[i] = new DFA(_ATN.GetDecisionState(i), i); 151 | } 152 | } 153 | private static int[] _serializedATN = { 154 | 4,0,41,401,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7, 155 | 6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14, 156 | 7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7,19,2,20,7,20,2,21, 157 | 7,21,2,22,7,22,2,23,7,23,2,24,7,24,2,25,7,25,2,26,7,26,2,27,7,27,2,28, 158 | 7,28,2,29,7,29,2,30,7,30,2,31,7,31,2,32,7,32,2,33,7,33,2,34,7,34,2,35, 159 | 7,35,2,36,7,36,2,37,7,37,2,38,7,38,2,39,7,39,2,40,7,40,2,41,7,41,2,42, 160 | 7,42,2,43,7,43,2,44,7,44,2,45,7,45,2,46,7,46,2,47,7,47,2,48,7,48,2,49, 161 | 7,49,2,50,7,50,2,51,7,51,2,52,7,52,2,53,7,53,2,54,7,54,2,55,7,55,2,56, 162 | 7,56,2,57,7,57,2,58,7,58,2,59,7,59,2,60,7,60,2,61,7,61,2,62,7,62,2,63, 163 | 7,63,2,64,7,64,2,65,7,65,2,66,7,66,2,67,7,67,2,68,7,68,2,69,7,69,2,70, 164 | 7,70,2,71,7,71,1,0,1,0,1,1,1,1,1,2,1,2,1,2,1,3,1,3,1,3,1,4,1,4,1,5,1,5, 165 | 1,6,1,6,1,7,1,7,1,7,1,8,1,8,1,9,1,9,1,9,1,10,1,10,1,10,1,11,1,11,1,12, 166 | 1,12,1,12,1,13,1,13,1,14,1,14,1,14,1,15,1,15,1,15,1,16,1,16,1,16,1,17, 167 | 1,17,1,18,1,18,1,19,1,19,1,20,1,20,1,21,1,21,1,22,1,22,1,23,1,23,1,24, 168 | 1,24,1,24,1,25,1,25,1,26,1,26,1,27,1,27,1,28,1,28,1,28,1,28,1,28,1,29, 169 | 1,29,1,29,1,29,1,29,1,29,1,30,1,30,1,30,1,30,1,31,1,31,1,31,1,32,1,32, 170 | 1,32,1,32,1,33,1,33,1,33,5,33,237,8,33,10,33,12,33,240,9,33,1,34,4,34, 171 | 243,8,34,11,34,12,34,244,1,35,5,35,248,8,35,10,35,12,35,251,9,35,1,35, 172 | 1,35,4,35,255,8,35,11,35,12,35,256,1,35,3,35,260,8,35,1,35,4,35,263,8, 173 | 35,11,35,12,35,264,1,35,1,35,5,35,269,8,35,10,35,12,35,272,9,35,1,35,3, 174 | 35,275,8,35,1,35,4,35,278,8,35,11,35,12,35,279,1,35,1,35,3,35,284,8,35, 175 | 1,36,1,36,1,36,1,36,5,36,290,8,36,10,36,12,36,293,9,36,5,36,295,8,36,10, 176 | 36,12,36,298,9,36,1,36,1,36,1,37,1,37,5,37,304,8,37,10,37,12,37,307,9, 177 | 37,1,37,1,37,1,38,1,38,5,38,313,8,38,10,38,12,38,316,9,38,1,38,1,38,1, 178 | 39,1,39,3,39,322,8,39,1,39,4,39,325,8,39,11,39,12,39,326,1,40,1,40,1,41, 179 | 1,41,1,42,1,42,1,42,3,42,336,8,42,1,43,1,43,1,44,1,44,1,44,1,44,1,44,1, 180 | 44,1,45,1,45,1,45,1,45,1,46,1,46,1,47,1,47,1,48,1,48,1,49,1,49,1,50,1, 181 | 50,1,51,1,51,1,52,1,52,1,53,1,53,1,54,1,54,1,55,1,55,1,56,1,56,1,57,1, 182 | 57,1,58,1,58,1,59,1,59,1,60,1,60,1,61,1,61,1,62,1,62,1,63,1,63,1,64,1, 183 | 64,1,65,1,65,1,66,1,66,1,67,1,67,1,68,1,68,1,69,1,69,1,70,1,70,1,71,1, 184 | 71,1,291,0,72,1,1,3,2,5,3,7,4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,12, 185 | 25,13,27,14,29,15,31,16,33,17,35,18,37,19,39,20,41,21,43,22,45,23,47,24, 186 | 49,25,51,26,53,27,55,28,57,29,59,30,61,31,63,32,65,33,67,34,69,35,71,36, 187 | 73,37,75,38,77,39,79,40,81,0,83,0,85,0,87,0,89,0,91,41,93,0,95,0,97,0, 188 | 99,0,101,0,103,0,105,0,107,0,109,0,111,0,113,0,115,0,117,0,119,0,121,0, 189 | 123,0,125,0,127,0,129,0,131,0,133,0,135,0,137,0,139,0,141,0,143,0,1,0, 190 | 34,3,0,0,31,39,39,92,92,1,0,35,35,1,0,93,93,2,0,69,69,101,101,2,0,43,43, 191 | 45,45,3,0,65,90,95,95,97,122,5,0,39,39,92,92,110,110,114,114,116,116,3, 192 | 0,48,57,65,70,97,102,3,0,9,10,12,13,32,32,2,0,65,65,97,97,2,0,66,66,98, 193 | 98,2,0,67,67,99,99,2,0,68,68,100,100,2,0,70,70,102,102,2,0,71,71,103,103, 194 | 2,0,72,72,104,104,2,0,73,73,105,105,2,0,74,74,106,106,2,0,75,75,107,107, 195 | 2,0,76,76,108,108,2,0,77,77,109,109,2,0,78,78,110,110,2,0,79,79,111,111, 196 | 2,0,80,80,112,112,2,0,81,81,113,113,2,0,82,82,114,114,2,0,83,83,115,115, 197 | 2,0,84,84,116,116,2,0,85,85,117,117,2,0,86,86,118,118,2,0,87,87,119,119, 198 | 2,0,88,88,120,120,2,0,89,89,121,121,2,0,90,90,122,122,389,0,1,1,0,0,0, 199 | 0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0,0,13,1,0, 200 | 0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,23,1,0,0,0,0, 201 | 25,1,0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,31,1,0,0,0,0,33,1,0,0,0,0,35,1, 202 | 0,0,0,0,37,1,0,0,0,0,39,1,0,0,0,0,41,1,0,0,0,0,43,1,0,0,0,0,45,1,0,0,0, 203 | 0,47,1,0,0,0,0,49,1,0,0,0,0,51,1,0,0,0,0,53,1,0,0,0,0,55,1,0,0,0,0,57, 204 | 1,0,0,0,0,59,1,0,0,0,0,61,1,0,0,0,0,63,1,0,0,0,0,65,1,0,0,0,0,67,1,0,0, 205 | 0,0,69,1,0,0,0,0,71,1,0,0,0,0,73,1,0,0,0,0,75,1,0,0,0,0,77,1,0,0,0,0,79, 206 | 1,0,0,0,0,91,1,0,0,0,1,145,1,0,0,0,3,147,1,0,0,0,5,149,1,0,0,0,7,152,1, 207 | 0,0,0,9,155,1,0,0,0,11,157,1,0,0,0,13,159,1,0,0,0,15,161,1,0,0,0,17,164, 208 | 1,0,0,0,19,166,1,0,0,0,21,169,1,0,0,0,23,172,1,0,0,0,25,174,1,0,0,0,27, 209 | 177,1,0,0,0,29,179,1,0,0,0,31,182,1,0,0,0,33,185,1,0,0,0,35,188,1,0,0, 210 | 0,37,190,1,0,0,0,39,192,1,0,0,0,41,194,1,0,0,0,43,196,1,0,0,0,45,198,1, 211 | 0,0,0,47,200,1,0,0,0,49,202,1,0,0,0,51,205,1,0,0,0,53,207,1,0,0,0,55,209, 212 | 1,0,0,0,57,211,1,0,0,0,59,216,1,0,0,0,61,222,1,0,0,0,63,226,1,0,0,0,65, 213 | 229,1,0,0,0,67,233,1,0,0,0,69,242,1,0,0,0,71,283,1,0,0,0,73,285,1,0,0, 214 | 0,75,301,1,0,0,0,77,310,1,0,0,0,79,319,1,0,0,0,81,328,1,0,0,0,83,330,1, 215 | 0,0,0,85,332,1,0,0,0,87,337,1,0,0,0,89,339,1,0,0,0,91,345,1,0,0,0,93,349, 216 | 1,0,0,0,95,351,1,0,0,0,97,353,1,0,0,0,99,355,1,0,0,0,101,357,1,0,0,0,103, 217 | 359,1,0,0,0,105,361,1,0,0,0,107,363,1,0,0,0,109,365,1,0,0,0,111,367,1, 218 | 0,0,0,113,369,1,0,0,0,115,371,1,0,0,0,117,373,1,0,0,0,119,375,1,0,0,0, 219 | 121,377,1,0,0,0,123,379,1,0,0,0,125,381,1,0,0,0,127,383,1,0,0,0,129,385, 220 | 1,0,0,0,131,387,1,0,0,0,133,389,1,0,0,0,135,391,1,0,0,0,137,393,1,0,0, 221 | 0,139,395,1,0,0,0,141,397,1,0,0,0,143,399,1,0,0,0,145,146,5,63,0,0,146, 222 | 2,1,0,0,0,147,148,5,58,0,0,148,4,1,0,0,0,149,150,5,124,0,0,150,151,5,124, 223 | 0,0,151,6,1,0,0,0,152,153,5,38,0,0,153,154,5,38,0,0,154,8,1,0,0,0,155, 224 | 156,5,124,0,0,156,10,1,0,0,0,157,158,5,94,0,0,158,12,1,0,0,0,159,160,5, 225 | 38,0,0,160,14,1,0,0,0,161,162,5,61,0,0,162,163,5,61,0,0,163,16,1,0,0,0, 226 | 164,165,5,61,0,0,165,18,1,0,0,0,166,167,5,33,0,0,167,168,5,61,0,0,168, 227 | 20,1,0,0,0,169,170,5,60,0,0,170,171,5,62,0,0,171,22,1,0,0,0,172,173,5, 228 | 60,0,0,173,24,1,0,0,0,174,175,5,60,0,0,175,176,5,61,0,0,176,26,1,0,0,0, 229 | 177,178,5,62,0,0,178,28,1,0,0,0,179,180,5,62,0,0,180,181,5,61,0,0,181, 230 | 30,1,0,0,0,182,183,5,60,0,0,183,184,5,60,0,0,184,32,1,0,0,0,185,186,5, 231 | 62,0,0,186,187,5,62,0,0,187,34,1,0,0,0,188,189,5,43,0,0,189,36,1,0,0,0, 232 | 190,191,5,45,0,0,191,38,1,0,0,0,192,193,5,42,0,0,193,40,1,0,0,0,194,195, 233 | 5,47,0,0,195,42,1,0,0,0,196,197,5,37,0,0,197,44,1,0,0,0,198,199,5,33,0, 234 | 0,199,46,1,0,0,0,200,201,5,126,0,0,201,48,1,0,0,0,202,203,5,42,0,0,203, 235 | 204,5,42,0,0,204,50,1,0,0,0,205,206,5,40,0,0,206,52,1,0,0,0,207,208,5, 236 | 41,0,0,208,54,1,0,0,0,209,210,5,44,0,0,210,56,1,0,0,0,211,212,3,131,65, 237 | 0,212,213,3,127,63,0,213,214,3,133,66,0,214,215,3,101,50,0,215,58,1,0, 238 | 0,0,216,217,3,103,51,0,217,218,3,93,46,0,218,219,3,115,57,0,219,220,3, 239 | 129,64,0,220,221,3,101,50,0,221,60,1,0,0,0,222,223,3,93,46,0,223,224,3, 240 | 119,59,0,224,225,3,99,49,0,225,62,1,0,0,0,226,227,3,121,60,0,227,228,3, 241 | 127,63,0,228,64,1,0,0,0,229,230,3,119,59,0,230,231,3,121,60,0,231,232, 242 | 3,131,65,0,232,66,1,0,0,0,233,238,3,81,40,0,234,237,3,81,40,0,235,237, 243 | 3,83,41,0,236,234,1,0,0,0,236,235,1,0,0,0,237,240,1,0,0,0,238,236,1,0, 244 | 0,0,238,239,1,0,0,0,239,68,1,0,0,0,240,238,1,0,0,0,241,243,3,83,41,0,242, 245 | 241,1,0,0,0,243,244,1,0,0,0,244,242,1,0,0,0,244,245,1,0,0,0,245,70,1,0, 246 | 0,0,246,248,3,83,41,0,247,246,1,0,0,0,248,251,1,0,0,0,249,247,1,0,0,0, 247 | 249,250,1,0,0,0,250,252,1,0,0,0,251,249,1,0,0,0,252,254,5,46,0,0,253,255, 248 | 3,83,41,0,254,253,1,0,0,0,255,256,1,0,0,0,256,254,1,0,0,0,256,257,1,0, 249 | 0,0,257,259,1,0,0,0,258,260,3,79,39,0,259,258,1,0,0,0,259,260,1,0,0,0, 250 | 260,284,1,0,0,0,261,263,3,83,41,0,262,261,1,0,0,0,263,264,1,0,0,0,264, 251 | 262,1,0,0,0,264,265,1,0,0,0,265,266,1,0,0,0,266,270,5,46,0,0,267,269,3, 252 | 83,41,0,268,267,1,0,0,0,269,272,1,0,0,0,270,268,1,0,0,0,270,271,1,0,0, 253 | 0,271,274,1,0,0,0,272,270,1,0,0,0,273,275,3,79,39,0,274,273,1,0,0,0,274, 254 | 275,1,0,0,0,275,284,1,0,0,0,276,278,3,83,41,0,277,276,1,0,0,0,278,279, 255 | 1,0,0,0,279,277,1,0,0,0,279,280,1,0,0,0,280,281,1,0,0,0,281,282,3,79,39, 256 | 0,282,284,1,0,0,0,283,249,1,0,0,0,283,262,1,0,0,0,283,277,1,0,0,0,284, 257 | 72,1,0,0,0,285,296,5,39,0,0,286,295,3,85,42,0,287,291,8,0,0,0,288,290, 258 | 9,0,0,0,289,288,1,0,0,0,290,293,1,0,0,0,291,292,1,0,0,0,291,289,1,0,0, 259 | 0,292,295,1,0,0,0,293,291,1,0,0,0,294,286,1,0,0,0,294,287,1,0,0,0,295, 260 | 298,1,0,0,0,296,294,1,0,0,0,296,297,1,0,0,0,297,299,1,0,0,0,298,296,1, 261 | 0,0,0,299,300,5,39,0,0,300,74,1,0,0,0,301,305,5,35,0,0,302,304,8,1,0,0, 262 | 303,302,1,0,0,0,304,307,1,0,0,0,305,303,1,0,0,0,305,306,1,0,0,0,306,308, 263 | 1,0,0,0,307,305,1,0,0,0,308,309,5,35,0,0,309,76,1,0,0,0,310,314,5,91,0, 264 | 0,311,313,8,2,0,0,312,311,1,0,0,0,313,316,1,0,0,0,314,312,1,0,0,0,314, 265 | 315,1,0,0,0,315,317,1,0,0,0,316,314,1,0,0,0,317,318,5,93,0,0,318,78,1, 266 | 0,0,0,319,321,7,3,0,0,320,322,7,4,0,0,321,320,1,0,0,0,321,322,1,0,0,0, 267 | 322,324,1,0,0,0,323,325,3,83,41,0,324,323,1,0,0,0,325,326,1,0,0,0,326, 268 | 324,1,0,0,0,326,327,1,0,0,0,327,80,1,0,0,0,328,329,7,5,0,0,329,82,1,0, 269 | 0,0,330,331,2,48,57,0,331,84,1,0,0,0,332,335,5,92,0,0,333,336,7,6,0,0, 270 | 334,336,3,89,44,0,335,333,1,0,0,0,335,334,1,0,0,0,336,86,1,0,0,0,337,338, 271 | 7,7,0,0,338,88,1,0,0,0,339,340,5,117,0,0,340,341,3,87,43,0,341,342,3,87, 272 | 43,0,342,343,3,87,43,0,343,344,3,87,43,0,344,90,1,0,0,0,345,346,7,8,0, 273 | 0,346,347,1,0,0,0,347,348,6,45,0,0,348,92,1,0,0,0,349,350,7,9,0,0,350, 274 | 94,1,0,0,0,351,352,7,10,0,0,352,96,1,0,0,0,353,354,7,11,0,0,354,98,1,0, 275 | 0,0,355,356,7,12,0,0,356,100,1,0,0,0,357,358,7,3,0,0,358,102,1,0,0,0,359, 276 | 360,7,13,0,0,360,104,1,0,0,0,361,362,7,14,0,0,362,106,1,0,0,0,363,364, 277 | 7,15,0,0,364,108,1,0,0,0,365,366,7,16,0,0,366,110,1,0,0,0,367,368,7,17, 278 | 0,0,368,112,1,0,0,0,369,370,7,18,0,0,370,114,1,0,0,0,371,372,7,19,0,0, 279 | 372,116,1,0,0,0,373,374,7,20,0,0,374,118,1,0,0,0,375,376,7,21,0,0,376, 280 | 120,1,0,0,0,377,378,7,22,0,0,378,122,1,0,0,0,379,380,7,23,0,0,380,124, 281 | 1,0,0,0,381,382,7,24,0,0,382,126,1,0,0,0,383,384,7,25,0,0,384,128,1,0, 282 | 0,0,385,386,7,26,0,0,386,130,1,0,0,0,387,388,7,27,0,0,388,132,1,0,0,0, 283 | 389,390,7,28,0,0,390,134,1,0,0,0,391,392,7,29,0,0,392,136,1,0,0,0,393, 284 | 394,7,30,0,0,394,138,1,0,0,0,395,396,7,31,0,0,396,140,1,0,0,0,397,398, 285 | 7,32,0,0,398,142,1,0,0,0,399,400,7,33,0,0,400,144,1,0,0,0,20,0,236,238, 286 | 244,249,256,259,264,270,274,279,283,291,294,296,305,314,321,326,335,1, 287 | 6,0,0 288 | }; 289 | 290 | public static readonly ATN _ATN = 291 | new ATNDeserializer().Deserialize(_serializedATN); 292 | 293 | 294 | } 295 | -------------------------------------------------------------------------------- /src/NCalcAsync/Domain/EvaluationVisitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace NCalcAsync.Domain 8 | { 9 | public class EvaluationVisitor : LogicalExpressionVisitor 10 | { 11 | private delegate T Func(); 12 | 13 | private readonly EvaluateOptions _options; 14 | private readonly EvaluateParameterAsyncHandler _evaluateParameterAsync; 15 | private readonly EvaluateFunctionAsyncHandler _evaluateFunctionAsync; 16 | private readonly NumberConversionTypePreference _numberConversionTypePreference; 17 | 18 | private bool IgnoreCase => (_options & EvaluateOptions.IgnoreCase) == EvaluateOptions.IgnoreCase; 19 | 20 | public EvaluationVisitor(EvaluateOptions options, EvaluateParameterAsyncHandler evaluateParameterAsync, EvaluateFunctionAsyncHandler evaluateFunctionAsync, NumberConversionTypePreference numberConversionTypePreference = NumberConversionTypePreference.Decimal) 21 | { 22 | _options = options; 23 | _evaluateParameterAsync = evaluateParameterAsync; 24 | _evaluateFunctionAsync = evaluateFunctionAsync; 25 | _numberConversionTypePreference = numberConversionTypePreference; 26 | } 27 | 28 | public object Result { get; private set; } 29 | 30 | private async Task EvaluateAsync(LogicalExpression expression) 31 | { 32 | await expression.AcceptAsync(this); 33 | return Result; 34 | } 35 | 36 | public override Task VisitAsync(LogicalExpression expression) 37 | { 38 | return Task.FromException(new Exception("The method or operation is not implemented.")); 39 | } 40 | 41 | private static readonly Type[] CommonTypes = { typeof(Int64), typeof(Double), typeof(Boolean), typeof(String), typeof(Decimal) }; 42 | 43 | /// 44 | /// Gets the the most precise type. 45 | /// 46 | /// Type a. 47 | /// Type b. 48 | /// 49 | private static Type GetMostPreciseType(Type a, Type b) 50 | { 51 | foreach (Type t in CommonTypes) 52 | { 53 | if (a == t || b == t) 54 | { 55 | return t; 56 | } 57 | } 58 | 59 | return a; 60 | } 61 | 62 | public int CompareUsingMostPreciseType(object a, object b) 63 | { 64 | Type mpt; 65 | if (a == null) 66 | { 67 | if (b == null) 68 | return 0; 69 | mpt = GetMostPreciseType(null, b.GetType()); 70 | } 71 | else 72 | { 73 | mpt = GetMostPreciseType(a.GetType(), b?.GetType()); 74 | } 75 | 76 | bool isCaseSensitiveComparer = (_options & EvaluateOptions.CaseInsensitiveComparer) == 0; 77 | 78 | var comparer = isCaseSensitiveComparer ? (IComparer)Comparer.Default : CaseInsensitiveComparer.Default; 79 | 80 | return comparer.Compare(Convert.ChangeType(a, mpt), Convert.ChangeType(b, mpt)); 81 | } 82 | 83 | public override async Task VisitAsync(TernaryExpression expression) 84 | { 85 | // Evaluates the left expression and saves the value 86 | await expression.LeftExpression.AcceptAsync(this); 87 | bool left = Convert.ToBoolean(Result); 88 | 89 | if (left) 90 | { 91 | await expression.MiddleExpression.AcceptAsync(this); 92 | } 93 | else 94 | { 95 | await expression.RightExpression.AcceptAsync(this); 96 | } 97 | } 98 | 99 | private static bool IsReal(object value) 100 | { 101 | var typeCode = Type.GetTypeCode(value.GetType()); 102 | 103 | return typeCode == TypeCode.Decimal || typeCode == TypeCode.Double || typeCode == TypeCode.Single; 104 | } 105 | 106 | public override async Task VisitAsync(BinaryExpression expression) 107 | { 108 | // simulate Lazy> behavior for late evaluation 109 | object leftValue = null; 110 | async Task Left() 111 | { 112 | if (leftValue == null) 113 | { 114 | await expression.LeftExpression.AcceptAsync(this); 115 | leftValue = Result; 116 | } 117 | 118 | return leftValue; 119 | } 120 | 121 | // simulate Lazy> behavior for late evaluation 122 | object rightValue = null; 123 | async Task Right() 124 | { 125 | if (rightValue == null) 126 | { 127 | await expression.RightExpression.AcceptAsync(this); 128 | rightValue = Result; 129 | } 130 | 131 | return rightValue; 132 | } 133 | 134 | switch (expression.Type) 135 | { 136 | case BinaryExpressionType.And: 137 | Result = Convert.ToBoolean(await Left()) && Convert.ToBoolean(await Right()); 138 | break; 139 | 140 | case BinaryExpressionType.Or: 141 | Result = Convert.ToBoolean(await Left()) || Convert.ToBoolean(await Right()); 142 | break; 143 | 144 | case BinaryExpressionType.Div: 145 | Result = IsReal(await Left()) || IsReal(await Right()) 146 | ? Numbers.Divide(await Left(), await Right(), _numberConversionTypePreference) 147 | : Numbers.Divide(Convert.ToDouble(await Left()), await Right(), _numberConversionTypePreference); 148 | break; 149 | 150 | case BinaryExpressionType.Equal: 151 | // Use the type of the left operand to make the comparison 152 | Result = CompareUsingMostPreciseType(await Left(), await Right()) == 0; 153 | break; 154 | 155 | case BinaryExpressionType.Greater: 156 | // Use the type of the left operand to make the comparison 157 | Result = CompareUsingMostPreciseType(await Left(), await Right()) > 0; 158 | break; 159 | 160 | case BinaryExpressionType.GreaterOrEqual: 161 | // Use the type of the left operand to make the comparison 162 | Result = CompareUsingMostPreciseType(await Left(), await Right()) >= 0; 163 | break; 164 | 165 | case BinaryExpressionType.Lesser: 166 | // Use the type of the left operand to make the comparison 167 | Result = CompareUsingMostPreciseType(await Left(), await Right()) < 0; 168 | break; 169 | 170 | case BinaryExpressionType.LesserOrEqual: 171 | // Use the type of the left operand to make the comparison 172 | Result = CompareUsingMostPreciseType(await Left(), await Right()) <= 0; 173 | break; 174 | 175 | case BinaryExpressionType.Minus: 176 | Result = Numbers.Soustract(await Left(), await Right(), _numberConversionTypePreference); 177 | break; 178 | 179 | case BinaryExpressionType.Modulo: 180 | Result = Numbers.Modulo(await Left(), await Right(), _numberConversionTypePreference); 181 | break; 182 | 183 | case BinaryExpressionType.NotEqual: 184 | // Use the type of the left operand to make the comparison 185 | Result = CompareUsingMostPreciseType(await Left(), await Right()) != 0; 186 | break; 187 | 188 | case BinaryExpressionType.Plus: 189 | if (await Left() is string) 190 | { 191 | Result = String.Concat(await Left(), await Right()); 192 | } 193 | else 194 | { 195 | Result = Numbers.Add(await Left(), await Right(), _numberConversionTypePreference); 196 | } 197 | 198 | break; 199 | 200 | case BinaryExpressionType.Times: 201 | Result = Numbers.Multiply(await Left(), await Right(), _numberConversionTypePreference); 202 | break; 203 | 204 | case BinaryExpressionType.BitwiseAnd: 205 | Result = Convert.ToUInt16(await Left()) & Convert.ToUInt16(await Right()); 206 | break; 207 | 208 | case BinaryExpressionType.BitwiseOr: 209 | Result = Convert.ToUInt16(await Left()) | Convert.ToUInt16(await Right()); 210 | break; 211 | 212 | case BinaryExpressionType.BitwiseXOr: 213 | Result = Convert.ToUInt16(await Left()) ^ Convert.ToUInt16(await Right()); 214 | break; 215 | 216 | case BinaryExpressionType.LeftShift: 217 | Result = Convert.ToUInt16(await Left()) << Convert.ToUInt16(await Right()); 218 | break; 219 | 220 | case BinaryExpressionType.RightShift: 221 | Result = Convert.ToUInt16(await Left()) >> Convert.ToUInt16(await Right()); 222 | break; 223 | 224 | case BinaryExpressionType.Exponentiation: 225 | Result = Math.Pow(Convert.ToDouble(await Left()), Convert.ToDouble(await Right())); 226 | break; 227 | } 228 | } 229 | 230 | public override async Task VisitAsync(UnaryExpression expression) 231 | { 232 | // Recursively evaluates the underlying expression 233 | await expression.Expression.AcceptAsync(this); 234 | 235 | switch (expression.Type) 236 | { 237 | case UnaryExpressionType.Not: 238 | Result = !Convert.ToBoolean(Result); 239 | break; 240 | 241 | case UnaryExpressionType.Negate: 242 | Result = Numbers.Soustract(0, Result, _numberConversionTypePreference); 243 | break; 244 | 245 | case UnaryExpressionType.BitwiseNot: 246 | Result = ~Convert.ToUInt16(Result); 247 | break; 248 | 249 | case UnaryExpressionType.Positive: 250 | // No-op 251 | break; 252 | } 253 | } 254 | 255 | public override Task VisitAsync(ValueExpression expression) 256 | { 257 | Result = expression.Value; 258 | return Task.CompletedTask; 259 | } 260 | 261 | public override async Task VisitAsync(Function function) 262 | { 263 | var args = new FunctionArgs 264 | { 265 | Parameters = new Expression[function.Expressions.Length] 266 | }; 267 | 268 | // Don't call parameters right now, instead let the function do it as needed. 269 | // Some parameters shouldn't be called, for instance, in a if(), the "not" value might be a division by zero 270 | // Evaluating every value could produce unexpected behaviour 271 | for (int i = 0; i < function.Expressions.Length; i++) 272 | { 273 | args.Parameters[i] = new Expression(function.Expressions[i], _options) 274 | { 275 | // Assign the parameters of the Expression to the arguments so that custom Functions and Parameters can use them 276 | Parameters = Parameters, 277 | 278 | // Pass on the parameter and function evaluators, if any 279 | EvaluateParameterAsync = _evaluateParameterAsync, 280 | EvaluateFunctionAsync = _evaluateFunctionAsync 281 | }; 282 | 283 | } 284 | 285 | if (_evaluateFunctionAsync != null) 286 | { 287 | var name = IgnoreCase ? function.Identifier.Name.ToLower() : function.Identifier.Name; 288 | 289 | // Calls external implementations, which may be a MulticastDelegate which 290 | // requires manual handling for async delegates. 291 | foreach (var handler in _evaluateFunctionAsync.GetInvocationList().Cast()) 292 | { 293 | await handler.Invoke(name, args); 294 | } 295 | } 296 | 297 | // If an external implementation was found get the result back 298 | if (args.HasResult) 299 | { 300 | Result = args.Result; 301 | return; 302 | } 303 | 304 | switch (function.Identifier.Name.ToLower()) 305 | { 306 | #region Abs 307 | case "abs": 308 | 309 | CheckCase("Abs", function.Identifier.Name); 310 | 311 | if (function.Expressions.Length != 1) 312 | throw new ArgumentException("Abs() takes exactly 1 argument"); 313 | 314 | Result = Math.Abs(Convert.ToDecimal( 315 | await EvaluateAsync(function.Expressions[0])) 316 | ); 317 | 318 | break; 319 | 320 | #endregion 321 | 322 | #region Acos 323 | case "acos": 324 | 325 | CheckCase("Acos", function.Identifier.Name); 326 | 327 | if (function.Expressions.Length != 1) 328 | throw new ArgumentException("Acos() takes exactly 1 argument"); 329 | 330 | Result = Math.Acos(Convert.ToDouble(await EvaluateAsync(function.Expressions[0]))); 331 | 332 | break; 333 | 334 | #endregion 335 | 336 | #region Asin 337 | case "asin": 338 | 339 | CheckCase("Asin", function.Identifier.Name); 340 | 341 | if (function.Expressions.Length != 1) 342 | throw new ArgumentException("Asin() takes exactly 1 argument"); 343 | 344 | Result = Math.Asin(Convert.ToDouble(await EvaluateAsync(function.Expressions[0]))); 345 | 346 | break; 347 | 348 | #endregion 349 | 350 | #region Atan 351 | case "atan": 352 | 353 | CheckCase("Atan", function.Identifier.Name); 354 | 355 | if (function.Expressions.Length != 1) 356 | throw new ArgumentException("Atan() takes exactly 1 argument"); 357 | 358 | Result = Math.Atan(Convert.ToDouble(await EvaluateAsync(function.Expressions[0]))); 359 | 360 | break; 361 | 362 | #endregion 363 | 364 | #region Atan2 365 | case "atan2": 366 | 367 | CheckCase("Atan2", function.Identifier.Name); 368 | 369 | if (function.Expressions.Length != 2) 370 | throw new ArgumentException("Atan2() takes exactly 2 argument"); 371 | 372 | Result = Math.Atan2(Convert.ToDouble(await EvaluateAsync(function.Expressions[0])), Convert.ToDouble(await EvaluateAsync(function.Expressions[1]))); 373 | 374 | break; 375 | 376 | #endregion 377 | 378 | #region Ceiling 379 | case "ceiling": 380 | 381 | CheckCase("Ceiling", function.Identifier.Name); 382 | 383 | if (function.Expressions.Length != 1) 384 | throw new ArgumentException("Ceiling() takes exactly 1 argument"); 385 | 386 | Result = Math.Ceiling(Convert.ToDouble(await EvaluateAsync(function.Expressions[0]))); 387 | 388 | break; 389 | 390 | #endregion 391 | 392 | #region Cos 393 | 394 | case "cos": 395 | 396 | CheckCase("Cos", function.Identifier.Name); 397 | 398 | if (function.Expressions.Length != 1) 399 | throw new ArgumentException("Cos() takes exactly 1 argument"); 400 | 401 | Result = Math.Cos(Convert.ToDouble(await EvaluateAsync(function.Expressions[0]))); 402 | 403 | break; 404 | 405 | #endregion 406 | 407 | #region Exp 408 | case "exp": 409 | 410 | CheckCase("Exp", function.Identifier.Name); 411 | 412 | if (function.Expressions.Length != 1) 413 | throw new ArgumentException("Exp() takes exactly 1 argument"); 414 | 415 | Result = Math.Exp(Convert.ToDouble(await EvaluateAsync(function.Expressions[0]))); 416 | 417 | break; 418 | 419 | #endregion 420 | 421 | #region Floor 422 | case "floor": 423 | 424 | CheckCase("Floor", function.Identifier.Name); 425 | 426 | if (function.Expressions.Length != 1) 427 | throw new ArgumentException("Floor() takes exactly 1 argument"); 428 | 429 | Result = Math.Floor(Convert.ToDouble(await EvaluateAsync(function.Expressions[0]))); 430 | 431 | break; 432 | 433 | #endregion 434 | 435 | #region IEEERemainder 436 | case "ieeeremainder": 437 | 438 | CheckCase("IEEERemainder", function.Identifier.Name); 439 | 440 | if (function.Expressions.Length != 2) 441 | throw new ArgumentException("IEEERemainder() takes exactly 2 arguments"); 442 | 443 | Result = Math.IEEERemainder(Convert.ToDouble(await EvaluateAsync(function.Expressions[0])), Convert.ToDouble(await EvaluateAsync(function.Expressions[1]))); 444 | 445 | break; 446 | 447 | #endregion 448 | 449 | #region Ln 450 | case "ln": 451 | 452 | CheckCase("Ln", function.Identifier.Name); 453 | 454 | if (function.Expressions.Length != 1) 455 | throw new ArgumentException("Ln() takes exactly 1 argument"); 456 | 457 | Result = Math.Log(Convert.ToDouble(await EvaluateAsync(function.Expressions[0]))); 458 | 459 | break; 460 | 461 | #endregion 462 | 463 | #region Log 464 | case "log": 465 | 466 | CheckCase("Log", function.Identifier.Name); 467 | 468 | if (function.Expressions.Length != 2) 469 | throw new ArgumentException("Log() takes exactly 2 arguments"); 470 | 471 | Result = Math.Log(Convert.ToDouble(await EvaluateAsync(function.Expressions[0])), Convert.ToDouble(await EvaluateAsync(function.Expressions[1]))); 472 | 473 | break; 474 | 475 | #endregion 476 | 477 | #region Log10 478 | case "log10": 479 | 480 | CheckCase("Log10", function.Identifier.Name); 481 | 482 | if (function.Expressions.Length != 1) 483 | throw new ArgumentException("Log10() takes exactly 1 argument"); 484 | 485 | Result = Math.Log10(Convert.ToDouble(await EvaluateAsync(function.Expressions[0]))); 486 | 487 | break; 488 | 489 | #endregion 490 | 491 | #region Pow 492 | case "pow": 493 | 494 | CheckCase("Pow", function.Identifier.Name); 495 | 496 | if (function.Expressions.Length != 2) 497 | throw new ArgumentException("Pow() takes exactly 2 arguments"); 498 | 499 | Result = Math.Pow(Convert.ToDouble(await EvaluateAsync(function.Expressions[0])), Convert.ToDouble(await EvaluateAsync(function.Expressions[1]))); 500 | 501 | break; 502 | 503 | #endregion 504 | 505 | #region Round 506 | case "round": 507 | 508 | CheckCase("Round", function.Identifier.Name); 509 | 510 | if (function.Expressions.Length != 2) 511 | throw new ArgumentException("Round() takes exactly 2 arguments"); 512 | 513 | MidpointRounding rounding = (_options & EvaluateOptions.RoundAwayFromZero) == EvaluateOptions.RoundAwayFromZero ? MidpointRounding.AwayFromZero : MidpointRounding.ToEven; 514 | 515 | Result = Math.Round(Convert.ToDouble(await EvaluateAsync(function.Expressions[0])), Convert.ToInt16(await EvaluateAsync(function.Expressions[1])), rounding); 516 | 517 | break; 518 | 519 | #endregion 520 | 521 | #region Sign 522 | case "sign": 523 | 524 | CheckCase("Sign", function.Identifier.Name); 525 | 526 | if (function.Expressions.Length != 1) 527 | throw new ArgumentException("Sign() takes exactly 1 argument"); 528 | 529 | Result = Math.Sign(Convert.ToDouble(await EvaluateAsync(function.Expressions[0]))); 530 | 531 | break; 532 | 533 | #endregion 534 | 535 | #region Sin 536 | case "sin": 537 | 538 | CheckCase("Sin", function.Identifier.Name); 539 | 540 | if (function.Expressions.Length != 1) 541 | throw new ArgumentException("Sin() takes exactly 1 argument"); 542 | 543 | Result = Math.Sin(Convert.ToDouble(await EvaluateAsync(function.Expressions[0]))); 544 | 545 | break; 546 | 547 | #endregion 548 | 549 | #region Sqrt 550 | case "sqrt": 551 | 552 | CheckCase("Sqrt", function.Identifier.Name); 553 | 554 | if (function.Expressions.Length != 1) 555 | throw new ArgumentException("Sqrt() takes exactly 1 argument"); 556 | 557 | Result = Math.Sqrt(Convert.ToDouble(await EvaluateAsync(function.Expressions[0]))); 558 | 559 | break; 560 | 561 | #endregion 562 | 563 | #region Tan 564 | case "tan": 565 | 566 | CheckCase("Tan", function.Identifier.Name); 567 | 568 | if (function.Expressions.Length != 1) 569 | throw new ArgumentException("Tan() takes exactly 1 argument"); 570 | 571 | Result = Math.Tan(Convert.ToDouble(await EvaluateAsync(function.Expressions[0]))); 572 | 573 | break; 574 | 575 | #endregion 576 | 577 | #region Truncate 578 | case "truncate": 579 | 580 | CheckCase("Truncate", function.Identifier.Name); 581 | 582 | if (function.Expressions.Length != 1) 583 | throw new ArgumentException("Truncate() takes exactly 1 argument"); 584 | 585 | Result = Math.Truncate(Convert.ToDouble(await EvaluateAsync(function.Expressions[0]))); 586 | 587 | break; 588 | 589 | #endregion 590 | 591 | #region Max 592 | case "max": 593 | 594 | CheckCase("Max", function.Identifier.Name); 595 | 596 | if (function.Expressions.Length != 2) 597 | throw new ArgumentException("Max() takes exactly 2 arguments"); 598 | 599 | object maxleft = await EvaluateAsync(function.Expressions[0]); 600 | object maxright = await EvaluateAsync(function.Expressions[1]); 601 | 602 | Result = Numbers.Max(maxleft, maxright, _numberConversionTypePreference); 603 | break; 604 | 605 | #endregion 606 | 607 | #region Min 608 | case "min": 609 | 610 | CheckCase("Min", function.Identifier.Name); 611 | 612 | if (function.Expressions.Length != 2) 613 | throw new ArgumentException("Min() takes exactly 2 arguments"); 614 | 615 | object minleft = await EvaluateAsync(function.Expressions[0]); 616 | object minright = await EvaluateAsync(function.Expressions[1]); 617 | 618 | Result = Numbers.Min(minleft, minright, _numberConversionTypePreference); 619 | break; 620 | 621 | #endregion 622 | 623 | #region if 624 | case "if": 625 | 626 | CheckCase("if", function.Identifier.Name); 627 | 628 | if (function.Expressions.Length != 3) 629 | throw new ArgumentException("if() takes exactly 3 arguments"); 630 | 631 | bool cond = Convert.ToBoolean(await EvaluateAsync(function.Expressions[0])); 632 | 633 | Result = cond ? await EvaluateAsync(function.Expressions[1]) : await EvaluateAsync(function.Expressions[2]); 634 | break; 635 | 636 | #endregion 637 | 638 | #region in 639 | case "in": 640 | 641 | CheckCase("in", function.Identifier.Name); 642 | 643 | if (function.Expressions.Length < 2) 644 | throw new ArgumentException("in() takes at least 2 arguments"); 645 | 646 | object parameter = await EvaluateAsync(function.Expressions[0]); 647 | 648 | bool evaluation = false; 649 | 650 | // Goes through any values, and stop whe one is found 651 | for (int i = 1; i < function.Expressions.Length; i++) 652 | { 653 | object argument = await EvaluateAsync(function.Expressions[i]); 654 | if (CompareUsingMostPreciseType(parameter, argument) == 0) 655 | { 656 | evaluation = true; 657 | break; 658 | } 659 | } 660 | 661 | Result = evaluation; 662 | break; 663 | 664 | #endregion 665 | 666 | default: 667 | throw new ArgumentException("Function not found", 668 | function.Identifier.Name); 669 | } 670 | } 671 | 672 | private void CheckCase(string function, string called) 673 | { 674 | if (IgnoreCase) 675 | { 676 | if (function.ToLower() == called.ToLower()) 677 | { 678 | return; 679 | } 680 | 681 | throw new ArgumentException("Function not found", called); 682 | } 683 | 684 | if (function != called) 685 | { 686 | throw new ArgumentException(String.Format("Function not found {0}. Try {1} instead.", called, function)); 687 | } 688 | } 689 | 690 | public override async Task VisitAsync(Identifier parameter) 691 | { 692 | if (Parameters.ContainsKey(parameter.Name)) 693 | { 694 | // The parameter is defined in the hashtable 695 | if (Parameters[parameter.Name] is Expression expression) 696 | { 697 | // The parameter is itself another Expression 698 | 699 | // Overloads parameters 700 | foreach (var p in Parameters) 701 | { 702 | expression.Parameters[p.Key] = p.Value; 703 | } 704 | 705 | Result = await expression.EvaluateAsync(_evaluateParameterAsync, _evaluateFunctionAsync); 706 | } 707 | else 708 | Result = Parameters[parameter.Name]; 709 | } 710 | else 711 | { 712 | // The parameter should be defined in a call back method 713 | var args = new ParameterArgs(); 714 | 715 | if (_evaluateParameterAsync != null) 716 | { 717 | var name = IgnoreCase ? parameter.Name.ToLower() : parameter.Name; 718 | 719 | // Calls external implementations, which may be a MulticastDelegate which 720 | // requires manual handling for async delegates. 721 | foreach (var handler in _evaluateParameterAsync.GetInvocationList().Cast()) 722 | { 723 | await handler.Invoke(name, args); 724 | } 725 | } 726 | 727 | if (!args.HasResult) 728 | throw new ArgumentException("Parameter was not defined", parameter.Name); 729 | 730 | Result = args.Result; 731 | } 732 | } 733 | 734 | public Dictionary Parameters { get; set; } 735 | } 736 | } -------------------------------------------------------------------------------- /test/NCalcAsync.Tests/Fixtures.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Threading.Tasks; 7 | 8 | namespace NCalcAsync.Tests 9 | { 10 | [TestClass] 11 | public class Fixtures 12 | { 13 | /// 14 | ///Gets or sets the test context which provides 15 | ///information about and functionality for the current test run. 16 | /// 17 | public TestContext TestContext { get; set; } 18 | 19 | [TestMethod] 20 | public async Task ExpressionShouldEvaluate() 21 | { 22 | var expressions = new[] 23 | { 24 | "2 + 3 + 5", 25 | "2 * 3 + 5", 26 | "2 * (3 + 5)", 27 | "2 * (2*(2*(2+1)))", 28 | "10 % 3", 29 | "true or false", 30 | "not true", 31 | "false || not (false and true)", 32 | "3 > 2 and 1 <= (3-2)", 33 | "3 % 2 != 10 % 3" 34 | }; 35 | 36 | foreach (string expression in expressions) 37 | Console.WriteLine("{0} = {1}", 38 | expression, 39 | await new Expression(expression).EvaluateAsync()); 40 | } 41 | 42 | [TestMethod] 43 | public async Task ShouldParseValues() 44 | { 45 | Assert.AreEqual(123456, await new Expression("123456").EvaluateAsync()); 46 | Assert.AreEqual(new DateTime(2001, 01, 01), await new Expression("#01/01/2001#").EvaluateAsync()); 47 | Assert.AreEqual(0.2d, await new Expression(".2").EvaluateAsync()); 48 | Assert.AreEqual(123.456d, await new Expression("123.456").EvaluateAsync()); 49 | Assert.AreEqual(123d, await new Expression("123.").EvaluateAsync()); 50 | Assert.AreEqual(12300d, await new Expression("123.E2").EvaluateAsync()); 51 | Assert.AreEqual(true, await new Expression("true").EvaluateAsync()); 52 | Assert.AreEqual("true", await new Expression("'true'").EvaluateAsync()); 53 | Assert.AreEqual("azerty", await new Expression("'azerty'").EvaluateAsync()); 54 | } 55 | 56 | [TestMethod] 57 | public async Task ShouldHandleUnicode() 58 | { 59 | Assert.AreEqual("経済協力開発機構", await new Expression("'経済協力開発機構'").EvaluateAsync()); 60 | Assert.AreEqual("Hello", await new Expression(@"'\u0048\u0065\u006C\u006C\u006F'").EvaluateAsync()); 61 | Assert.AreEqual("だ", await new Expression(@"'\u3060'").EvaluateAsync()); 62 | Assert.AreEqual("\u0100", await new Expression(@"'\u0100'").EvaluateAsync()); 63 | } 64 | 65 | [TestMethod] 66 | public async Task ShouldEscapeCharacters() 67 | { 68 | Assert.AreEqual("'hello'", await new Expression(@"'\'hello\''").EvaluateAsync()); 69 | Assert.AreEqual(" ' hel lo ' ", await new Expression(@"' \' hel lo \' '").EvaluateAsync()); 70 | Assert.AreEqual("hel\nlo", await new Expression(@"'hel\nlo'").EvaluateAsync()); 71 | } 72 | 73 | [TestMethod] 74 | public async Task ShouldDisplayErrorMessages() 75 | { 76 | try 77 | { 78 | await new Expression("(3 + 2").EvaluateAsync(); 79 | Assert.Fail(); 80 | } 81 | catch (EvaluationException e) 82 | { 83 | Console.WriteLine("Error catched: " + e.Message); 84 | } 85 | } 86 | 87 | [TestMethod] 88 | public async Task Maths() 89 | { 90 | Assert.AreEqual(1M, await new Expression("Abs(-1)").EvaluateAsync()); 91 | Assert.AreEqual(0d, await new Expression("Acos(1)").EvaluateAsync()); 92 | Assert.AreEqual(0d, await new Expression("Asin(0)").EvaluateAsync()); 93 | Assert.AreEqual(0d, await new Expression("Atan(0)").EvaluateAsync()); 94 | Assert.AreEqual(2d, await new Expression("Ceiling(1.5)").EvaluateAsync()); 95 | Assert.AreEqual(1d, await new Expression("Cos(0)").EvaluateAsync()); 96 | Assert.AreEqual(1d, await new Expression("Exp(0)").EvaluateAsync()); 97 | Assert.AreEqual(1d, await new Expression("Floor(1.5)").EvaluateAsync()); 98 | Assert.AreEqual(-1d, await new Expression("IEEERemainder(3,2)").EvaluateAsync()); 99 | Assert.AreEqual(0d, await new Expression("Ln(1)").EvaluateAsync()); 100 | Assert.AreEqual(0d, await new Expression("Log(1,10)").EvaluateAsync()); 101 | Assert.AreEqual(0d, await new Expression("Log10(1)").EvaluateAsync()); 102 | Assert.AreEqual(9d, await new Expression("Pow(3,2)").EvaluateAsync()); 103 | Assert.AreEqual(3.22d, await new Expression("Round(3.222,2)").EvaluateAsync()); 104 | Assert.AreEqual(-1, await new Expression("Sign(-10)").EvaluateAsync()); 105 | Assert.AreEqual(0d, await new Expression("Sin(0)").EvaluateAsync()); 106 | Assert.AreEqual(2d, await new Expression("Sqrt(4)").EvaluateAsync()); 107 | Assert.AreEqual(0d, await new Expression("Tan(0)").EvaluateAsync()); 108 | Assert.AreEqual(1d, await new Expression("Truncate(1.7)").EvaluateAsync()); 109 | Assert.AreEqual(-Math.PI / 2, (double)await new Expression("Atan2(-1,0)").EvaluateAsync(), 1e-16); 110 | Assert.AreEqual(Math.PI / 2, (double)await new Expression("Atan2(1,0)").EvaluateAsync(), 1e-16); 111 | Assert.AreEqual(Math.PI, (double)await new Expression("Atan2(0,-1)").EvaluateAsync(), 1e-16); 112 | Assert.AreEqual(0, (double)await new Expression("Atan2(0,1)").EvaluateAsync(), 1e-16); 113 | } 114 | 115 | [TestMethod] 116 | public async Task ExpressionShouldEvaluateCustomFunctions() 117 | { 118 | var e = new Expression("SecretOperation(3, 6)"); 119 | 120 | e.EvaluateFunctionAsync = async (name, args) => 121 | { 122 | if (name == "SecretOperation") 123 | args.Result = (int)await args.Parameters[0].EvaluateAsync() + (int)await args.Parameters[1].EvaluateAsync(); 124 | }; 125 | 126 | Assert.AreEqual(9, await e.EvaluateAsync()); 127 | } 128 | 129 | [TestMethod] 130 | public async Task ExpressionShouldEvaluateCustomFunctionsWithParameters() 131 | { 132 | var e = new Expression("SecretOperation([e], 6) + f"); 133 | e.Parameters["e"] = 3; 134 | e.Parameters["f"] = 1; 135 | 136 | e.EvaluateFunctionAsync = async (name, args) => 137 | { 138 | if (name == "SecretOperation") 139 | args.Result = (int)await args.Parameters[0].EvaluateAsync() + (int)await args.Parameters[1].EvaluateAsync(); 140 | }; 141 | 142 | Assert.AreEqual(10, await e.EvaluateAsync()); 143 | } 144 | 145 | [TestMethod] 146 | public async Task ExpressionShouldEvaluateParameters() 147 | { 148 | var e = new Expression("Round(Pow(Pi, 2) + Pow([Pi Squared], 2) + [X], 2)"); 149 | 150 | e.Parameters["Pi Squared"] = new Expression("Pi * [Pi]"); 151 | e.Parameters["X"] = 10; 152 | 153 | e.EvaluateParameterAsync = (name, args) => 154 | { 155 | if (name == "Pi") 156 | args.Result = 3.14; 157 | 158 | return Task.CompletedTask; 159 | }; 160 | 161 | Assert.AreEqual(117.07, await e.EvaluateAsync()); 162 | } 163 | 164 | [TestMethod] 165 | public async Task ShouldEvaluateConditionnal() 166 | { 167 | var eif = new Expression("if([divider] <> 0, [divided] / [divider], 0)"); 168 | eif.Parameters["divider"] = 5; 169 | eif.Parameters["divided"] = 5; 170 | 171 | Assert.AreEqual(1d, await eif.EvaluateAsync()); 172 | 173 | eif = new Expression("if([divider] <> 0, [divided] / [divider], 0)"); 174 | eif.Parameters["divider"] = 0; 175 | eif.Parameters["divided"] = 5; 176 | Assert.AreEqual(0, await eif.EvaluateAsync()); 177 | } 178 | 179 | [TestMethod] 180 | public async Task ShouldOverrideExistingFunctions() 181 | { 182 | var e = new Expression("Round(1.99, 2)"); 183 | 184 | Assert.AreEqual(1.99d, await e.EvaluateAsync()); 185 | 186 | e.EvaluateFunctionAsync = (name, args) => 187 | { 188 | if (name == "Round") 189 | args.Result = 3; 190 | 191 | return Task.CompletedTask; 192 | }; 193 | 194 | Assert.AreEqual(3, await e.EvaluateAsync()); 195 | } 196 | 197 | [TestMethod] 198 | public async Task ShouldEvaluateInOperator() 199 | { 200 | // The last argument should not be evaluated 201 | var ein = new Expression("in((2 + 2), [1], [2], 1 + 2, 4, 1 / 0)"); 202 | ein.Parameters["1"] = 2; 203 | ein.Parameters["2"] = 5; 204 | 205 | Assert.AreEqual(true, await ein.EvaluateAsync()); 206 | 207 | var eout = new Expression("in((2 + 2), [1], [2], 1 + 2, 3)"); 208 | eout.Parameters["1"] = 2; 209 | eout.Parameters["2"] = 5; 210 | 211 | Assert.AreEqual(false, await eout.EvaluateAsync()); 212 | 213 | // Should work with strings 214 | var estring = new Expression("in('to' + 'to', 'titi', 'toto')"); 215 | 216 | Assert.AreEqual(true, await estring.EvaluateAsync()); 217 | 218 | } 219 | 220 | [TestMethod] 221 | public async Task ShouldEvaluateOperators() 222 | { 223 | var expressions = new Dictionary 224 | { 225 | {"!true", false}, 226 | {"not false", true}, 227 | {"Not false", true}, 228 | {"NOT false", true}, 229 | {"-10", -10}, 230 | {"+20", 20}, 231 | {"2**-1", 0.5}, 232 | {"2**+2", 4.0}, 233 | {"2 * 3", 6}, 234 | {"6 / 2", 3d}, 235 | {"7 % 2", 1}, 236 | {"2 + 3", 5}, 237 | {"2 - 1", 1}, 238 | {"1 < 2", true}, 239 | {"1 > 2", false}, 240 | {"1 <= 2", true}, 241 | {"1 <= 1", true}, 242 | {"1 >= 2", false}, 243 | {"1 >= 1", true}, 244 | {"1 = 1", true}, 245 | {"1 == 1", true}, 246 | {"1 != 1", false}, 247 | {"1 <> 1", false}, 248 | {"1 & 1", 1}, 249 | {"1 | 1", 1}, 250 | {"1 ^ 1", 0}, 251 | {"~1", ~1}, 252 | {"2 >> 1", 1}, 253 | {"2 << 1", 4}, 254 | {"true && false", false}, 255 | {"True and False", false}, 256 | {"tRue aNd faLse", false}, 257 | {"TRUE ANd fALSE", false}, 258 | {"true AND FALSE", false}, 259 | {"true || false", true}, 260 | {"true or false", true}, 261 | {"true Or false", true}, 262 | {"true OR false", true}, 263 | {"if(true, 0, 1)", 0}, 264 | {"if(false, 0, 1)", 1} 265 | }; 266 | 267 | foreach (KeyValuePair pair in expressions) 268 | { 269 | Assert.AreEqual(pair.Value, await new Expression(pair.Key).EvaluateAsync(), pair.Key + " failed"); 270 | } 271 | 272 | } 273 | 274 | [TestMethod] 275 | public async Task ShouldHandleOperatorsPriority() 276 | { 277 | Assert.AreEqual(8, await new Expression("2+2+2+2").EvaluateAsync()); 278 | Assert.AreEqual(16, await new Expression("2*2*2*2").EvaluateAsync()); 279 | Assert.AreEqual(6, await new Expression("2*2+2").EvaluateAsync()); 280 | Assert.AreEqual(6, await new Expression("2+2*2").EvaluateAsync()); 281 | 282 | Assert.AreEqual(9d, await new Expression("1 + 2 + 3 * 4 / 2").EvaluateAsync()); 283 | Assert.AreEqual(13.5, await new Expression("18/2/2*3").EvaluateAsync()); 284 | 285 | Assert.AreEqual(-1d, await new Expression("-1 ** 2").EvaluateAsync()); 286 | Assert.AreEqual(1d, await new Expression("(-1) ** 2").EvaluateAsync()); 287 | Assert.AreEqual(512d, await new Expression("2 ** 3 ** 2").EvaluateAsync()); 288 | Assert.AreEqual(64d, await new Expression("(2 ** 3) ** 2").EvaluateAsync()); 289 | Assert.AreEqual(18d, await new Expression("2 * 3 ** 2").EvaluateAsync()); 290 | Assert.AreEqual(8d, await new Expression("2 ** 4 / 2").EvaluateAsync()); 291 | } 292 | 293 | [TestMethod] 294 | public async Task ShouldNotLoosePrecision() 295 | { 296 | Assert.AreEqual(0.5, await new Expression("3/6").EvaluateAsync()); 297 | } 298 | 299 | [TestMethod] 300 | public async Task ShouldThrowAnExpcetionWhenInvalidNumber() 301 | { 302 | try 303 | { 304 | await new Expression(". + 2").EvaluateAsync(); 305 | Assert.Fail(); 306 | } 307 | catch (EvaluationException e) 308 | { 309 | Console.WriteLine("Error caught: " + e.Message); 310 | } 311 | } 312 | 313 | [TestMethod] 314 | public async Task ShouldNotRoundDecimalValues() 315 | { 316 | Assert.AreEqual(false, await new Expression("0 <= -0.6").EvaluateAsync()); 317 | } 318 | 319 | [TestMethod] 320 | public async Task ShouldEvaluateTernaryExpression() 321 | { 322 | Assert.AreEqual(1, await new Expression("1+2<3 ? 3+4 : 1").EvaluateAsync()); 323 | } 324 | 325 | 326 | [TestMethod] 327 | public async Task ShouldHandleStringConcatenation() 328 | { 329 | Assert.AreEqual("toto", await new Expression("'to' + 'to'").EvaluateAsync()); 330 | Assert.AreEqual("one2", await new Expression("'one' + 2").EvaluateAsync()); 331 | Assert.AreEqual(3M, await new Expression("1 + '2'").EvaluateAsync()); 332 | } 333 | 334 | [TestMethod] 335 | public void ShouldDetectSyntaxErrorsBeforeEvaluation() 336 | { 337 | var e = new Expression("a + b * ("); 338 | Assert.IsNull(e.Error); 339 | Assert.IsTrue(e.HasErrors()); 340 | Assert.IsTrue(e.HasErrors()); 341 | Assert.IsNotNull(e.Error); 342 | 343 | e = new Expression("* b "); 344 | Assert.IsNull(e.Error); 345 | Assert.IsTrue(e.HasErrors()); 346 | Assert.IsNotNull(e.Error); 347 | } 348 | 349 | [TestMethod] 350 | public void ShouldReuseCompiledExpressionsInMultiThreadedMode() 351 | { 352 | Parallel.For(0, 10000, async i => 353 | { 354 | var expression = new Expression((i % 111).ToString()); 355 | 356 | var result = await expression.EvaluateAsync(); 357 | Assert.AreEqual(i % 111, result); 358 | }); 359 | } 360 | 361 | [TestMethod] 362 | public async Task ShouldHandleCaseSensitiveness() 363 | { 364 | Assert.AreEqual(1M, await new Expression("aBs(-1)", EvaluateOptions.IgnoreCase).EvaluateAsync()); 365 | Assert.AreEqual(1M, await new Expression("Abs(-1)", EvaluateOptions.None).EvaluateAsync()); 366 | 367 | try 368 | { 369 | Assert.AreEqual(1M, await new Expression("aBs(-1)", EvaluateOptions.None).EvaluateAsync()); 370 | } 371 | catch (ArgumentException) 372 | { 373 | return; 374 | } 375 | catch (Exception) 376 | { 377 | Assert.Fail("Unexpected exception"); 378 | } 379 | 380 | Assert.Fail("Should throw ArgumentException"); 381 | } 382 | 383 | [TestMethod] 384 | public async Task ShouldHandleCustomParametersWhenNoSpecificParameterIsDefined() 385 | { 386 | var e = new Expression("Round(Pow([Pi], 2) + Pow([Pi], 2) + 10, 2)"); 387 | 388 | e.EvaluateParameterAsync = (name, arg) => 389 | { 390 | if (name == "Pi") 391 | arg.Result = 3.14; 392 | 393 | return Task.CompletedTask; 394 | }; 395 | 396 | await e.EvaluateAsync(); 397 | } 398 | 399 | [TestMethod] 400 | public async Task ShouldHandleCustomFunctionsInFunctions() 401 | { 402 | var e = new Expression("if(true, func1(x) + func2(func3(y)), 0)"); 403 | 404 | e.EvaluateFunctionAsync = async (name, arg) => 405 | { 406 | switch (name) 407 | { 408 | case "func1": 409 | arg.Result = 1; 410 | break; 411 | case "func2": 412 | arg.Result = 2 * Convert.ToDouble(await arg.Parameters[0].EvaluateAsync()); 413 | break; 414 | case "func3": 415 | arg.Result = 3 * Convert.ToDouble(await arg.Parameters[0].EvaluateAsync()); 416 | break; 417 | } 418 | }; 419 | 420 | e.EvaluateParameterAsync = (name, arg) => 421 | { 422 | switch (name) 423 | { 424 | case "x": 425 | arg.Result = 1; 426 | break; 427 | case "y": 428 | arg.Result = 2; 429 | break; 430 | case "z": 431 | arg.Result = 3; 432 | break; 433 | } 434 | 435 | return Task.CompletedTask; 436 | }; 437 | 438 | Assert.AreEqual(13d, await e.EvaluateAsync()); 439 | } 440 | 441 | 442 | [TestMethod] 443 | public async Task ShouldParseScientificNotation() 444 | { 445 | Assert.AreEqual(12.2d, await new Expression("1.22e1").EvaluateAsync()); 446 | Assert.AreEqual(100d, await new Expression("1e2").EvaluateAsync()); 447 | Assert.AreEqual(100d, await new Expression("1e+2").EvaluateAsync()); 448 | Assert.AreEqual(0.01d, await new Expression("1e-2").EvaluateAsync()); 449 | Assert.AreEqual(0.001d, await new Expression(".1e-2").EvaluateAsync()); 450 | Assert.AreEqual(10000000000d, await new Expression("1e10").EvaluateAsync()); 451 | } 452 | 453 | [TestMethod] 454 | public async Task ShouldEvaluateArrayParameters() 455 | { 456 | var e = new Expression("x * x", EvaluateOptions.IterateParameters); 457 | e.Parameters["x"] = new[] { 0, 1, 2, 3, 4 }; 458 | 459 | var result = (IList)await e.EvaluateAsync(); 460 | 461 | Assert.AreEqual(0, result[0]); 462 | Assert.AreEqual(1, result[1]); 463 | Assert.AreEqual(4, result[2]); 464 | Assert.AreEqual(9, result[3]); 465 | Assert.AreEqual(16, result[4]); 466 | } 467 | 468 | [TestMethod] 469 | public async Task CustomFunctionShouldReturnNull() 470 | { 471 | var e = new Expression("SecretOperation(3, 6)"); 472 | 473 | e.EvaluateFunctionAsync = (name, args) => 474 | { 475 | Assert.IsFalse(args.HasResult); 476 | if (name == "SecretOperation") 477 | args.Result = null; 478 | Assert.IsTrue(args.HasResult); 479 | 480 | return Task.CompletedTask; 481 | }; 482 | 483 | Assert.AreEqual(null, await e.EvaluateAsync()); 484 | } 485 | 486 | [TestMethod] 487 | public async Task CustomParametersShouldReturnNull() 488 | { 489 | var e = new Expression("x"); 490 | 491 | e.EvaluateParameterAsync = (name, args) => 492 | { 493 | Assert.IsFalse(args.HasResult); 494 | if (name == "x") 495 | args.Result = null; 496 | Assert.IsTrue(args.HasResult); 497 | 498 | return Task.CompletedTask; 499 | }; 500 | 501 | Assert.AreEqual(null, await e.EvaluateAsync()); 502 | } 503 | 504 | [TestMethod] 505 | public async Task ShouldCompareDates() 506 | { 507 | Assert.AreEqual(true, await new Expression("#1/1/2009#==#1/1/2009#").EvaluateAsync()); 508 | Assert.AreEqual(false, await new Expression("#2/1/2009#==#1/1/2009#").EvaluateAsync()); 509 | } 510 | 511 | [TestMethod] 512 | public async Task ShouldRoundAwayFromZero() 513 | { 514 | Assert.AreEqual(22d, await new Expression("Round(22.5, 0)").EvaluateAsync()); 515 | Assert.AreEqual(23d, await new Expression("Round(22.5, 0)", EvaluateOptions.RoundAwayFromZero).EvaluateAsync()); 516 | } 517 | 518 | [TestMethod] 519 | public async Task ShouldEvaluateSubExpressions() 520 | { 521 | var volume = new Expression("[surface] * h"); 522 | var surface = new Expression("[l] * [L]"); 523 | volume.Parameters["surface"] = surface; 524 | volume.Parameters["h"] = 3; 525 | surface.Parameters["l"] = 1; 526 | surface.Parameters["L"] = 2; 527 | 528 | Assert.AreEqual(6, await volume.EvaluateAsync()); 529 | } 530 | 531 | [TestMethod] 532 | public async Task ShouldHandleLongValues() 533 | { 534 | Assert.AreEqual(40_000_000_000 + 1, await new Expression("40000000000+1").EvaluateAsync()); 535 | } 536 | 537 | [TestMethod] 538 | public async Task ShouldCompareLongValues() 539 | { 540 | Assert.AreEqual(false, await new Expression("(0=1500000)||(((0+2200000000)-1500000)<0)").EvaluateAsync()); 541 | } 542 | 543 | [TestMethod, ExpectedException(typeof(InvalidOperationException))] 544 | public async Task ShouldDisplayErrorIfUncompatibleTypes() 545 | { 546 | var e = new Expression("(a > b) + 10"); 547 | e.Parameters["a"] = 1; 548 | e.Parameters["b"] = 2; 549 | await e.EvaluateAsync(); 550 | } 551 | 552 | [TestMethod] 553 | public async Task ShouldNotConvertRealTypes() 554 | { 555 | var e = new Expression("x/2"); 556 | e.Parameters["x"] = 2F; 557 | Assert.AreEqual(typeof(float), (await e.EvaluateAsync()).GetType()); 558 | 559 | e = new Expression("x/2"); 560 | e.Parameters["x"] = 2D; 561 | Assert.AreEqual(typeof(double), (await e.EvaluateAsync()).GetType()); 562 | 563 | e = new Expression("x/2"); 564 | e.Parameters["x"] = 2m; 565 | Assert.AreEqual(typeof(decimal), (await e.EvaluateAsync()).GetType()); 566 | 567 | e = new Expression("a / b * 100"); 568 | e.Parameters["a"] = 20M; 569 | e.Parameters["b"] = 20M; 570 | Assert.AreEqual(100M, await e.EvaluateAsync()); 571 | 572 | } 573 | 574 | [TestMethod] 575 | public async Task ShouldShortCircuitBooleanExpressions() 576 | { 577 | var e = new Expression("([a] != 0) && ([b]/[a]>2)"); 578 | e.Parameters["a"] = 0; 579 | 580 | Assert.AreEqual(false, await e.EvaluateAsync()); 581 | } 582 | 583 | [TestMethod] 584 | public async Task ShouldAddDoubleAndDecimal() 585 | { 586 | var e = new Expression("1.8 + Abs([var1])"); 587 | e.Parameters["var1"] = 9.2; 588 | 589 | Assert.AreEqual(11M, await e.EvaluateAsync()); 590 | } 591 | 592 | [TestMethod] 593 | public async Task ShouldSubtractDoubleAndDecimal() 594 | { 595 | var e = new Expression("1.8 - Abs([var1])"); 596 | e.Parameters["var1"] = 0.8; 597 | 598 | Assert.AreEqual(1M, await e.EvaluateAsync()); 599 | } 600 | 601 | [TestMethod] 602 | public async Task ShouldMultiplyDoubleAndDecimal() 603 | { 604 | var e = new Expression("1.8 * Abs([var1])"); 605 | e.Parameters["var1"] = 9.2; 606 | 607 | Assert.AreEqual(16.56M, await e.EvaluateAsync()); 608 | } 609 | 610 | [TestMethod] 611 | public async Task ShouldDivideDoubleAndDecimal() 612 | { 613 | var e = new Expression("1.8 / Abs([var1])"); 614 | e.Parameters["var1"] = 0.5; 615 | 616 | Assert.AreEqual(3.6M, await e.EvaluateAsync()); 617 | } 618 | 619 | [TestMethod] 620 | public async Task ShouldHandleDelayedAsyncFunctionsAndParameters() 621 | { 622 | var e = new Expression("delay(200) + delay(fixed_delay)") 623 | { 624 | EvaluateFunctionAsync = async (name, args) => 625 | { 626 | if (name == "delay") 627 | { 628 | var ms = (int)await args.Parameters[0].EvaluateAsync(); 629 | await Task.Delay(ms); 630 | 631 | args.Result = ms; 632 | args.HasResult = true; 633 | } 634 | }, 635 | 636 | EvaluateParameterAsync = async (name, args) => 637 | { 638 | if (name == "fixed_delay") 639 | { 640 | var ms = 100; 641 | await Task.Delay(ms); 642 | 643 | args.Result = ms; 644 | args.HasResult = true; 645 | } 646 | } 647 | }; 648 | 649 | var stopWatch = new Stopwatch(); 650 | stopWatch.Start(); 651 | var result = await e.EvaluateAsync(); 652 | stopWatch.Stop(); 653 | 654 | Assert.AreEqual(300, result); 655 | 656 | // It's difficult to do exact timing tests in async code, but since delay(fixed_delay) will delay at least 200ms, 657 | // and delay(200) will do the same, we know it should take at least 200ms to run. 658 | Assert.IsTrue(stopWatch.ElapsedMilliseconds >= 200); 659 | } 660 | 661 | [TestMethod] 662 | public async Task MultipleEvaluateFunctionHandlersCanBeAdded() 663 | { 664 | var e = new Expression("a(10) + b(20)"); 665 | 666 | // Delaying in the first function handler that's added but not in the second 667 | // seems to reliably cause a() not to be found when the code doesn't handle 668 | // MulticastDelegate itself. (Timing-dependent unit tests are always suspect, though). 669 | e.EvaluateFunctionAsync += async (name, args) => 670 | { 671 | await Task.Delay(200); 672 | if (name == "a") 673 | { 674 | args.Result = 10 * (int)await args.Parameters[0].EvaluateAsync(); 675 | } 676 | }; 677 | 678 | e.EvaluateFunctionAsync += async (name, args) => 679 | { 680 | if (name == "b") 681 | { 682 | args.Result = 100 * (int)await args.Parameters[0].EvaluateAsync(); 683 | } 684 | }; 685 | 686 | Assert.AreEqual(2100, await e.EvaluateAsync()); 687 | } 688 | 689 | [TestMethod] 690 | public async Task MultipleEvaluateParameterHandlersCanBeAdded() 691 | { 692 | var e = new Expression("a + b"); 693 | 694 | // Same kind of timing as in the test above 695 | e.EvaluateParameterAsync += async (name, args) => 696 | { 697 | await Task.Delay(200); 698 | if (name == "a") 699 | { 700 | args.Result = 10; 701 | } 702 | }; 703 | 704 | e.EvaluateParameterAsync += (name, args) => 705 | { 706 | if (name == "b") 707 | { 708 | args.Result = 20; 709 | } 710 | 711 | return Task.CompletedTask; 712 | }; 713 | 714 | Assert.AreEqual(30, await e.EvaluateAsync()); 715 | } 716 | 717 | [TestMethod] 718 | public async Task IncorrectCalculation_Issue_4() 719 | { 720 | Expression e = new Expression("(1604326026000-1604325747000)/60000"); 721 | var evalutedResult = await e.EvaluateAsync(); 722 | 723 | Assert.IsInstanceOfType(evalutedResult, typeof(double)); 724 | Assert.AreEqual(4.65, (double)evalutedResult, 0.001); 725 | } 726 | 727 | [TestMethod] 728 | public void Should_Throw_Exception_On_Lexer_Errors_Issue_6() 729 | { 730 | // https://github.com/ncalc/ncalc-async/issues/6 731 | 732 | var result1 = Assert.ThrowsException(() => Expression.Compile("\"0\"", true)); 733 | Assert.AreEqual($"token recognition error at: '\"' at 1:1{Environment.NewLine}token recognition error at: '\"' at 1:3", result1.Message); 734 | 735 | var result2 = Assert.ThrowsException(() => Expression.Compile("Format(\"{0:(###) ###-####}\", \"9999999999\")", true)); 736 | Assert.IsTrue(result2.Message.StartsWith("String '' was not recognized as a valid DateTime.")); 737 | } 738 | 739 | [TestMethod] 740 | public async Task Should_Divide_Decimal_By_Double_Issue_16() 741 | { 742 | // https://github.com/ncalc/ncalc/issues/16 743 | 744 | var e = new Expression("x / 1.0"); 745 | e.Parameters["x"] = 1m; 746 | 747 | Assert.AreEqual(1m, await e.EvaluateAsync()); 748 | } 749 | 750 | [TestMethod] 751 | public async Task Should_Divide_Decimal_By_Single() 752 | { 753 | var e = new Expression("x / y"); 754 | e.Parameters["x"] = 1m; 755 | e.Parameters["y"] = 1f; 756 | 757 | Assert.AreEqual(1m, await e.EvaluateAsync()); 758 | } 759 | 760 | [TestMethod] 761 | public async Task Should_Do_Number_Conversion() 762 | { 763 | int intValue = 10; 764 | float floatValue = 10.0F; 765 | double doubleValue = 10.0; 766 | decimal decimalValue = 10.00M; 767 | string stringValue = "3703"; 768 | 769 | var decimalNumberConversions = new Dictionary() 770 | { 771 | { "[intValue] + [stringValue]", 3713M }, 772 | { "[floatValue] + [stringValue]", 3713M }, 773 | { "[doubleValue] + [stringValue]", 3713M }, 774 | { "[decimalValue] + [stringValue]", 3713M }, 775 | { "[stringValue] - [intValue]", 3693M }, 776 | { "[stringValue] - [doubleValue]", 3693M }, 777 | { "[stringValue] - [decimalValue]", 3693M }, 778 | { "[stringValue] - [stringValue]", 0M }, 779 | { "[stringValue] * [intValue]", 37030M }, 780 | { "[stringValue] * [doubleValue]", 37030M }, 781 | { "[stringValue] * [decimalValue]", 37030M }, 782 | { "[stringValue] * [stringValue]", 13712209M }, 783 | { "[stringValue] / [intValue]", 370.3D }, 784 | { "[stringValue] / [floatValue]", 370.3M }, 785 | { "[stringValue] / [doubleValue]", 370.3M }, 786 | { "[stringValue] / [decimalValue]", 370.3M }, 787 | { "[stringValue] / [stringValue]", 1M }, 788 | { "[stringValue] % [intValue]", 3M }, 789 | { "[stringValue] % [stringValue]", 0M } 790 | }; 791 | 792 | var doubleNumberConversions = new Dictionary() 793 | { 794 | { "[intValue] + [stringValue]", 3713D }, 795 | { "[floatValue] + [stringValue]", 3713D }, 796 | { "[doubleValue] + [stringValue]", 3713D }, 797 | { "[decimalValue] + [stringValue]", 3713M }, 798 | { "[stringValue] - [intValue]", 3693D }, 799 | { "[stringValue] - [doubleValue]", 3693D }, 800 | { "[stringValue] - [decimalValue]", 3693M }, 801 | { "[stringValue] - [stringValue]", 0D }, 802 | { "[stringValue] * [intValue]", 37030D }, 803 | { "[stringValue] * [doubleValue]", 37030D }, 804 | { "[stringValue] * [decimalValue]", 37030M }, 805 | { "[stringValue] * [stringValue]", 13712209D }, 806 | { "[stringValue] / [intValue]", 370.3D }, 807 | { "[stringValue] / [floatValue]", 370.3D }, 808 | { "[stringValue] / [doubleValue]", 370.3D }, 809 | { "[stringValue] / [decimalValue]", 370.3M }, 810 | { "[stringValue] / [stringValue]", 1D }, 811 | { "[stringValue] % [intValue]", 3D }, 812 | { "[stringValue] % [stringValue]", 0D } 813 | }; 814 | 815 | foreach (var (decimalNumberConversionExpression, calculatedValue) in decimalNumberConversions) 816 | { 817 | var expression = new Expression(decimalNumberConversionExpression, numberConversionTypePreference: NumberConversionTypePreference.Decimal); 818 | 819 | expression.EvaluateParameterAsync += (name, args) => 820 | { 821 | args.Result = name switch 822 | { 823 | "intValue" => intValue, 824 | "floatValue" => floatValue, 825 | "doubleValue" => doubleValue, 826 | "decimalValue" => decimalValue, 827 | "stringValue" => stringValue, 828 | _ => throw new NotImplementedException() 829 | }; 830 | return Task.CompletedTask; 831 | }; 832 | 833 | Assert.AreEqual(calculatedValue, await expression.EvaluateAsync()); 834 | } 835 | 836 | foreach (var (doubleNumberConversionExpression, calculatedValue) in doubleNumberConversions) 837 | { 838 | var expression = new Expression(doubleNumberConversionExpression, numberConversionTypePreference: NumberConversionTypePreference.Double); 839 | 840 | expression.EvaluateParameterAsync += (name, args) => 841 | { 842 | args.Result = name switch 843 | { 844 | "intValue" => intValue, 845 | "floatValue" => floatValue, 846 | "doubleValue" => doubleValue, 847 | "decimalValue" => decimalValue, 848 | "stringValue" => stringValue, 849 | _ => throw new NotImplementedException() 850 | }; 851 | return Task.CompletedTask; 852 | }; 853 | 854 | Assert.AreEqual(calculatedValue, await expression.EvaluateAsync()); 855 | } 856 | } 857 | 858 | 859 | [TestMethod] 860 | public async Task Should_Use_Case_Insensitive_Comparer_Issue_24() 861 | { 862 | var eif = new Expression("PageState == 'LIST'", EvaluateOptions.CaseInsensitiveComparer); 863 | eif.Parameters["PageState"] = "List"; 864 | 865 | Assert.AreEqual(true, await eif.EvaluateAsync()); 866 | } 867 | } 868 | } 869 | 870 | --------------------------------------------------------------------------------