├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ExpressionParser.Tests ├── ExpressionParser.Tests.csproj ├── ExpressionParserParseForTests.cs ├── ExpressionParserParseTests.cs ├── ExpressionParserUsingTests.cs ├── Extensions │ └── EnumerableExtensionsTests.cs ├── Properties │ └── AssemblyInfo.cs ├── TestDoubles │ ├── IOtherDummy.cs │ ├── ISomeDummy.cs │ ├── OtherDummy.cs │ ├── SomeDummy.cs │ └── SomeOther.cs └── packages.config ├── ExpressionParser.sln ├── ExpressionParser ├── Engine │ ├── Builder.cs │ └── Reader.cs ├── ExpressionParser.cs ├── ExpressionParser.csproj ├── ExpressionParser.nuspec ├── ExpressionParser.pfx ├── ExpressionParserImplementation.cs ├── Extensions │ ├── EnumerableExtensions.cs │ └── TypeExtensions.cs ├── IExpressionParser.cs ├── Model │ ├── Keywords.cs │ ├── NodeStack.cs │ ├── Nodes │ │ ├── AddNode.cs │ │ ├── AndNode.cs │ │ ├── ArrayIndexNode.cs │ │ ├── BinaryNode.cs │ │ ├── CoalesceNode.cs │ │ ├── DivideNode.cs │ │ ├── DotNode.cs │ │ ├── EqualNode.cs │ │ ├── GreaterNode.cs │ │ ├── GreaterOrEqualNode.cs │ │ ├── IdentifierNode.cs │ │ ├── LambdaNode.cs │ │ ├── LessNode.cs │ │ ├── LessOrEqualNode.cs │ │ ├── LiteralNode.cs │ │ ├── MethodNode.cs │ │ ├── ModuloNode.cs │ │ ├── MultiplyNode.cs │ │ ├── NegateNode.cs │ │ ├── Node.cs │ │ ├── NotEqualNode.cs │ │ ├── NotNode.cs │ │ ├── NullPropagationNode.cs │ │ ├── OperationNode.cs │ │ ├── OrNode.cs │ │ ├── PropertyNode.cs │ │ ├── SubtractNode.cs │ │ ├── TypeAsNode.cs │ │ ├── TypeCastNode.cs │ │ ├── TypeIsNode.cs │ │ ├── TypeNode.cs │ │ ├── UnaryNode.cs │ │ └── ValueNode.cs │ ├── TokenList.cs │ └── Tokens │ │ ├── LiteralToken.cs │ │ ├── NameToken.cs │ │ ├── SymbolToken.cs │ │ ├── Token.cs │ │ └── TypeToken.cs └── Properties │ └── AssemblyInfo.cs ├── FEATURES.md ├── GeneratePackage.cmd ├── Icon256.png ├── LICENSE.md └── README.md /.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 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | /BuildProcessTemplates/UpgradeTemplate.xaml 290 | /BuildProcessTemplates/LabDefaultTemplate.11.xaml 291 | /BuildProcessTemplates/DefaultTemplate.11.1.xaml 292 | /BuildProcessTemplates/AzureContinuousDeployment.11.xaml 293 | /ExpressionParser/FilterExpressionSyntax.txt 294 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | notifications: 2 | email: 3 | recipients: 4 | - andre.vianna.rj@hotmail.com 5 | 6 | # change is when the repo status goes from pass to fail or vice versa 7 | on_success: change 8 | on_failure: always 9 | 10 | language: csharp 11 | solution: ExpressionParser.sln 12 | install: 13 | - nuget restore ExpressionParser.sln 14 | - nuget install NUnit.Runners -Version 3.7.0 -OutputDirectory testrunner 15 | script: 16 | - xbuild /p:Configuration=Release ExpressionParser.sln 17 | - mono ./testrunner/NUnit.ConsoleRunner.3.7.0/tools/nunit3-console.exe ./ExpressionParser.Tests/bin/Release/ExpressionParser.Tests.dll 18 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at andre.vianna.rj@hotmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a build. 11 | 2. Update the README.md with details of changes to the interface, this includes new environment variables, exposed ports, useful file locations and container parameters. 12 | 3. Increase the version numbers in any examples files and the README.md to the new version that this Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 13 | 4. You may merge the Pull Request in once you have the sign-off of other developer, or if you do not have permission to do that, you may request the reviewer to merge it for you. 14 | 5. Before you submit your pull request make sure that you added/updated all the unit tests required by your changes. We will only accept changes that keep or increase the current code coverage. We will not accept any code excluded from code coverage in the main project. 15 | 16 | ## Code of Conduct 17 | 18 | ### Our Pledge 19 | 20 | In the interest of fostering an open and welcoming environment, we as 21 | contributors and maintainers pledge to making participation in our project and 22 | our community a harassment-free experience for everyone, regardless of age, body 23 | size, disability, ethnicity, gender identity and expression, level of experience, 24 | nationality, personal appearance, race, religion, or sexual identity and 25 | orientation. 26 | 27 | ### Our Standards 28 | 29 | Examples of behavior that contributes to creating a positive environment 30 | include: 31 | 32 | * Using welcoming and inclusive language 33 | * Being respectful of differing viewpoints and experiences 34 | * Gracefully accepting constructive criticism 35 | * Focusing on what is best for the community 36 | * Showing empathy towards other community members 37 | 38 | Examples of unacceptable behavior by participants include: 39 | 40 | * The use of sexualized language or imagery and unwelcome sexual attention or 41 | advances 42 | * Trolling, insulting/derogatory comments, and personal or political attacks 43 | * Public or private harassment 44 | * Publishing others' private information, such as a physical or electronic 45 | address, without explicit permission 46 | * Other conduct which could reasonably be considered inappropriate in a 47 | professional setting 48 | 49 | ### Our Responsibilities 50 | 51 | Project maintainers are responsible for clarifying the standards of acceptable 52 | behavior and are expected to take appropriate and fair corrective action in 53 | response to any instances of unacceptable behavior. 54 | 55 | Project maintainers have the right and responsibility to remove, edit, or 56 | reject comments, commits, code, wiki edits, issues, and other contributions 57 | that are not aligned to this Code of Conduct, or to ban temporarily or 58 | permanently any contributor for other behaviors that they deem inappropriate, 59 | threatening, offensive, or harmful. 60 | 61 | ### Scope 62 | 63 | This Code of Conduct applies both within project spaces and in public spaces 64 | when an individual is representing the project or its community. Examples of 65 | representing a project or community include using an official project e-mail 66 | address, posting via an official social media account, or acting as an appointed 67 | representative at an online or offline event. Representation of a project may be 68 | further defined and clarified by project maintainers. 69 | 70 | ### Enforcement 71 | 72 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 73 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 74 | complaints will be reviewed and investigated and will result in a response that 75 | is deemed necessary and appropriate to the circumstances. The project team is 76 | obligated to maintain confidentiality with regard to the reporter of an incident. 77 | Further details of specific enforcement policies may be posted separately. 78 | 79 | Project maintainers who do not follow or enforce the Code of Conduct in good 80 | faith may face temporary or permanent repercussions as determined by other 81 | members of the project's leadership. 82 | 83 | ### Attribution 84 | 85 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 86 | available at [http://contributor-covenant.org/version/1/4][version] 87 | 88 | [homepage]: http://contributor-covenant.org 89 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /ExpressionParser.Tests/ExpressionParser.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {D47E6ECF-68CD-4204-9DAA-B227FB4B41D8} 8 | Library 9 | Properties 10 | ExpressionParser.Tests 11 | ExpressionParser.Tests 12 | v4.5.1 13 | 512 14 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 15.0 16 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 17 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 18 | False 19 | UnitTest 20 | 21 | 22 | SAK 23 | SAK 24 | SAK 25 | SAK 26 | 27 | 28 | 29 | 30 | 31 | 15.0 32 | publish\ 33 | true 34 | Disk 35 | false 36 | Foreground 37 | 7 38 | Days 39 | false 40 | false 41 | true 42 | 0 43 | 1.0.0.%2a 44 | false 45 | false 46 | true 47 | 48 | 49 | true 50 | full 51 | false 52 | bin\Debug\ 53 | DEBUG;TRACE 54 | prompt 55 | 4 56 | false 57 | 58 | 59 | pdbonly 60 | true 61 | bin\Release\ 62 | TRACE 63 | prompt 64 | 4 65 | false 66 | 67 | 68 | 69 | ..\packages\NUnit.3.9.0\lib\net45\nunit.framework.dll 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | Designer 89 | 90 | 91 | 92 | 93 | {869cdc74-f58f-4600-8636-d0b0cccd4416} 94 | ExpressionParser 95 | 96 | 97 | 98 | 99 | False 100 | .NET Framework 3.5 SP1 101 | false 102 | 103 | 104 | 105 | 106 | 107 | 108 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /ExpressionParser.Tests/ExpressionParserParseForTests.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using ExpressionParser.Tests.TestDoubles; 3 | using NUnit.Framework; 4 | 5 | namespace ExpressionParser.Tests 6 | { 7 | [TestFixture] 8 | [ExcludeFromCodeCoverage] 9 | public class ExpressionParserParseForTests 10 | { 11 | private SomeDummy dummy; 12 | private OtherDummy other1; 13 | private OtherDummy other2; 14 | private OtherDummy other3; 15 | private SomeOther dummyOther1; 16 | private SomeOther dummyOther2; 17 | private SomeOther dummyOther3; 18 | 19 | [SetUp] 20 | public void Setup() 21 | { 22 | dummy = new SomeDummy(); 23 | other1 = new OtherDummy {StringProperty = "A"}; 24 | other2 = new OtherDummy {StringProperty = "B"}; 25 | other3 = new OtherDummy {StringProperty = "A"}; 26 | dummyOther1 = new SomeOther { Some = dummy, Other = other1 }; 27 | dummyOther2 = new SomeOther { Some = dummy, Other = other2 }; 28 | dummyOther3 = new SomeOther { Some = dummy, Other = other3 }; 29 | dummy.ManyNavigation.Add(dummyOther1); 30 | dummy.ManyNavigation.Add(dummyOther2); 31 | dummy.ManyNavigation.Add(dummyOther3); 32 | other1.ManyNavigation.Add(dummyOther1); 33 | other2.ManyNavigation.Add(dummyOther2); 34 | other3.ManyNavigation.Add(dummyOther3); 35 | } 36 | 37 | [TestCase("!TrueProperty", ExpectedResult = false)] 38 | [TestCase("!!TrueProperty", ExpectedResult = true)] 39 | [TestCase("TrueProperty && TrueProperty", ExpectedResult = true)] 40 | [TestCase("TrueProperty && FalseProperty", ExpectedResult = false)] 41 | [TestCase("FalseProperty && TrueProperty", ExpectedResult = false)] 42 | [TestCase("FalseProperty && FalseProperty", ExpectedResult = false)] 43 | [TestCase("!TrueProperty && TrueProperty", ExpectedResult = false)] 44 | [TestCase("TrueProperty && !TrueProperty", ExpectedResult = false)] 45 | [TestCase("TrueProperty || TrueProperty", ExpectedResult = true)] 46 | [TestCase("TrueProperty || FalseProperty", ExpectedResult = true)] 47 | [TestCase("FalseProperty || TrueProperty", ExpectedResult = true)] 48 | [TestCase("FalseProperty || FalseProperty", ExpectedResult = false)] 49 | [TestCase("!TrueProperty || TrueProperty", ExpectedResult = true)] 50 | [TestCase("!TrueProperty || FalseProperty", ExpectedResult = false)] 51 | [TestCase("TrueProperty || !FalseProperty", ExpectedResult = true)] 52 | [TestCase("!FalseProperty || TrueProperty", ExpectedResult = true)] 53 | [TestCase("!FalseProperty || FalseProperty", ExpectedResult = true)] 54 | [TestCase("(FalseProperty || TrueProperty) && FalseProperty", ExpectedResult = false)] 55 | [TestCase("FalseProperty || ((TrueProperty && FalseProperty) || TrueProperty)", ExpectedResult = true)] 56 | [TestCase("FalseProperty || (!(TrueProperty && FalseProperty) && TrueProperty)", ExpectedResult = true)] 57 | public object ExpressionParser_ParseFor_WithValid_LogicalOperators_ShouldPass(string input) 58 | { 59 | var result = ExpressionParser.ParseFor(input); 60 | return result(dummy); 61 | } 62 | 63 | [Test] 64 | public void ExpressionParser_ParseFor_WithParameterName_ShouldPass() 65 | { 66 | var result = ExpressionParser.ParseFor("FalseProperty || (!(TrueProperty && FalseProperty) && TrueProperty)", "p"); 67 | Assert.That(result.DynamicInvoke(dummy), Is.True); 68 | } 69 | 70 | [Test] 71 | public void ExpressionParser_ParseFor_WithParameterName_AndReturnType_ShouldPass() 72 | { 73 | var result = ExpressionParser.ParseFor("FalseProperty || (!(TrueProperty && FalseProperty) && TrueProperty)", "p"); 74 | Assert.That(result(dummy), Is.True); 75 | } 76 | 77 | [TestCase("FalseProperty == false", ExpectedResult = true)] 78 | [TestCase("FalseProperty == true", ExpectedResult = false)] 79 | [TestCase("FalseProperty != true", ExpectedResult = true)] 80 | [TestCase("FalseProperty && TrueProperty == false", ExpectedResult = false)] 81 | [TestCase("FalseProperty && (TrueProperty == false)", ExpectedResult = false)] 82 | [TestCase("(FalseProperty && TrueProperty) == false", ExpectedResult = true)] 83 | [TestCase("FalseProperty == TrueProperty && false", ExpectedResult = false)] 84 | [TestCase("(FalseProperty == TrueProperty) && false", ExpectedResult = false)] 85 | [TestCase("FalseProperty == (TrueProperty && false)", ExpectedResult = true)] 86 | [TestCase("(false == (true && false)) == (true && false)", ExpectedResult = false)] 87 | [TestCase("false == false && false == false && false == false", ExpectedResult = true)] 88 | [TestCase("false == (false && false) == false && false == false", ExpectedResult = false)] 89 | public object ExpressionParser_ParseFor_WithValid_ComparissionOperators_ShouldPass(string input) 90 | { 91 | var result = ExpressionParser.ParseFor(input); 92 | return result(dummy); 93 | } 94 | 95 | [TestCase("StringProperty ?? \"ABC\"", ExpectedResult = "Hello")] 96 | [TestCase("NullProperty ?? \"ABC\"", ExpectedResult = "ABC")] 97 | [TestCase("NullProperty?.Length ?? 4", ExpectedResult = 4)] 98 | public object ExpressionParser_ParseFor_WithValid_CoalesceForProperty_ShouldPass(string input) 99 | { 100 | var result = ExpressionParser.ParseFor(input); 101 | return result.DynamicInvoke(dummy); 102 | } 103 | 104 | [TestCase("TrueProperty", ExpectedResult = true)] 105 | [TestCase("FalseProperty", ExpectedResult = false)] 106 | [TestCase("IntProperty", ExpectedResult = 42)] 107 | [TestCase("DecimalProperty", ExpectedResult = 6.283184)] 108 | [TestCase("SingleNavigation.TrueProperty", ExpectedResult = true)] 109 | [TestCase("SingleNavigation.IntProperty", ExpectedResult = 7)] 110 | public object ExpressionParser_ParseFor_WithValid_PropertyReferences_ShouldPass(string input) 111 | { 112 | var result = ExpressionParser.ParseFor(input); 113 | return result.DynamicInvoke(dummy); 114 | } 115 | 116 | [TestCase("IntProperty.ToString()", ExpectedResult = "42")] 117 | [TestCase("StringProperty.StartsWith(\"Hell\")", ExpectedResult = true)] 118 | [TestCase("ArrayProperty.Any()", ExpectedResult = true)] 119 | [TestCase("StringProperty.Substring(2)", ExpectedResult = "llo")] 120 | [TestCase("StringProperty.Substring(2, 2)", ExpectedResult = "ll")] 121 | [TestCase("IntProperty.ToString().Count()", ExpectedResult = 2)] 122 | public object ExpressionParser_WithValid_MethodCalls_ShouldPass(string input) 123 | { 124 | var result = ExpressionParser.ParseFor(input); 125 | return result.DynamicInvoke(dummy); 126 | } 127 | 128 | [TestCase("ArrayProperty[1]", ExpectedResult = 2)] 129 | [TestCase("ArrayProperty[1+2]", ExpectedResult = 4)] 130 | public object ExpressionParser_ParseFor_WithValid_ArrayIndex_ShouldPass(string input) 131 | { 132 | var result = ExpressionParser.ParseFor(input); 133 | return result(dummy); 134 | } 135 | 136 | [TestCase("ArrayProperty.Any(i => i % 2 == 0)", ExpectedResult = true)] 137 | [TestCase("ArrayProperty.Where(i => i % 2 == 0)", ExpectedResult = new [] { 2, 4 })] 138 | [TestCase("ManyNavigation.Any(i => i.Other.StringProperty == \"A\")", ExpectedResult = true)] 139 | [TestCase("ManyNavigation.Any(i => i.Other.StringProperty == \"D\")", ExpectedResult = false)] 140 | [TestCase("StringProperty.Where(i => i == 'l')", ExpectedResult = new[] { 'l', 'l' })] 141 | public object ExpressionParser_ParseFor_WithValid_LambdaExpression_ShouldPass(string input) 142 | { 143 | var result = ExpressionParser.ParseFor(input); 144 | return result.DynamicInvoke(dummy); 145 | } 146 | 147 | [Test] 148 | public void ExpressionParser_ParseFor_WithValid_LambdaExpression_WithNamedParameter_ShouldPass() 149 | { 150 | var result = ExpressionParser.ParseFor("i == 'l'", "i"); 151 | Assert.That(result.DynamicInvoke('l'), Is.True); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /ExpressionParser.Tests/ExpressionParserParseTests.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using NUnit.Framework; 3 | 4 | namespace ExpressionParser.Tests 5 | { 6 | [TestFixture] 7 | [ExcludeFromCodeCoverage] 8 | public class ExpressionParserParseTests 9 | { 10 | [TestCase("null", ExpectedResult = null)] 11 | [TestCase("true", ExpectedResult = true)] 12 | [TestCase("false", ExpectedResult = false)] 13 | [TestCase("10", ExpectedResult = 10)] 14 | [TestCase("-7", ExpectedResult = -7)] 15 | [TestCase("+101", ExpectedResult = 101)] 16 | [TestCase("3.141592", ExpectedResult = 3.141592)] 17 | [TestCase("\"Hello Nurse!\"", ExpectedResult = "Hello Nurse!")] 18 | [TestCase("\"\tSome\tTabs.\"", ExpectedResult = "\tSome\tTabs.")] 19 | [TestCase("'\\t'", ExpectedResult = '\t')] 20 | [TestCase("'\\r'", ExpectedResult = '\r')] 21 | [TestCase("'\\n'", ExpectedResult = '\n')] 22 | [TestCase("'\\''", ExpectedResult = '\'')] 23 | [TestCase("'\\\\'", ExpectedResult = '\\')] 24 | [TestCase("'\"'", ExpectedResult = '"')] 25 | [TestCase("'a'", ExpectedResult = 'a')] 26 | public object ExpressionParser_Parse_WithValid_LiteralValues_ShouldPass(string input) 27 | { 28 | var result = ExpressionParser.Parse(input); 29 | return result.DynamicInvoke(); 30 | } 31 | 32 | [TestCase("\"Hello\" ?? \"ABC\"", ExpectedResult = "Hello")] 33 | [TestCase("null ?? \"ABC\"", ExpectedResult = "ABC")] 34 | public object ExpressionParser_Parse_WithValid_CoalesceForLiteral_ShouldPass(string input) 35 | { 36 | var result = ExpressionParser.Parse(input); 37 | return result(); 38 | } 39 | 40 | [TestCase("2 + 3", ExpectedResult = 5)] 41 | [TestCase("2 - 3", ExpectedResult = -1)] 42 | [TestCase("5 - 4 + 7", ExpectedResult = 8)] 43 | [TestCase("2 * 3", ExpectedResult = 6)] 44 | [TestCase("2 / 5", ExpectedResult = 0)] 45 | [TestCase("2.0 / 5.0", ExpectedResult = 0.4)] 46 | [TestCase("3 + 2 * 3", ExpectedResult = 9)] 47 | [TestCase("(3 + 2) * 3", ExpectedResult = 15)] 48 | [TestCase("37 % 3", ExpectedResult = 1)] 49 | public object ExpressionParser_Parse_WithValid_MathExpression_ShouldPass(string input) 50 | { 51 | var result = ExpressionParser.Parse(input); 52 | return result.DynamicInvoke(); 53 | } 54 | 55 | [TestCase("0 < 1", ExpectedResult = true)] 56 | [TestCase("0 <= 1", ExpectedResult = true)] 57 | [TestCase("1 <= 1", ExpectedResult = true)] 58 | [TestCase("2 > 1", ExpectedResult = true)] 59 | [TestCase("1 >= 1", ExpectedResult = true)] 60 | [TestCase("2 >= 1", ExpectedResult = true)] 61 | [TestCase("1 < 0", ExpectedResult = false)] 62 | [TestCase("1 < 1", ExpectedResult = false)] 63 | [TestCase("1 <= 0", ExpectedResult = false)] 64 | [TestCase("1 > 2", ExpectedResult = false)] 65 | [TestCase("1 > 1", ExpectedResult = false)] 66 | [TestCase("1 >= 2", ExpectedResult = false)] 67 | public object ExpressionParser_Parse_WithValid_NumericComparisson_ShouldPass(string input) 68 | { 69 | var result = ExpressionParser.Parse(input); 70 | return result(); 71 | } 72 | 73 | [TestCase("(int)1", ExpectedResult = 1)] 74 | [TestCase("(string)\"Hi\"", ExpectedResult = "Hi")] 75 | [TestCase("(decimal)3", ExpectedResult = 3.0)] 76 | [TestCase("(decimal)3 + 6.0", ExpectedResult = 9.0)] 77 | [TestCase("(int)5.0 / (int)3.0", ExpectedResult = 1)] 78 | [TestCase("1 is int", ExpectedResult = true)] 79 | [TestCase("\"Hi\" is string", ExpectedResult = true)] 80 | [TestCase("\"Hi\" is decimal", ExpectedResult = false)] 81 | [TestCase("\"Hi\" as decimal", ExpectedResult = null)] 82 | [TestCase("\"Hi\" as string", ExpectedResult = "Hi")] 83 | [TestCase("3.0 as decimal", ExpectedResult = 3.0)] 84 | public object ExpressionParser_Parse_WithValid_TypeOperations_ShouldPass(string input) 85 | { 86 | var result = ExpressionParser.Parse(input); 87 | return result.DynamicInvoke(); 88 | } 89 | 90 | [TestCase("abc")] 91 | [TestCase("abc()")] 92 | [TestCase("%&%&")] 93 | [TestCase("true false")] 94 | [TestCase("3 ?? 4")] 95 | [TestCase("'ab'")] 96 | [TestCase("t?b:c")] 97 | [TestCase("(int)abc")] 98 | [TestCase("(invalidType)abc")] 99 | [TestCase("abc is int")] 100 | [TestCase("abc as int == 3")] 101 | [TestCase("i => i")] 102 | [TestCase("i => i == 0")] 103 | public void ExpressionParser_Parse_WithInvalidInput_ShouldThrow(string input) 104 | { 105 | Assert.That(() => ExpressionParser.Parse(input), Throws.Exception); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /ExpressionParser.Tests/ExpressionParserUsingTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using ExpressionParser.Tests.TestDoubles; 5 | using NUnit.Framework; 6 | 7 | namespace ExpressionParser.Tests 8 | { 9 | [TestFixture] 10 | [ExcludeFromCodeCoverage] 11 | public class ExpressionParserUsingTests 12 | { 13 | private SomeDummy dummy; 14 | 15 | [SetUp] 16 | public void Setup() 17 | { 18 | dummy = new SomeDummy(); 19 | } 20 | 21 | 22 | [Test] 23 | public void ExpressionParser_UsingSingleType_WithValidInput_ShouldPass() 24 | { 25 | Assert.That(ExpressionParser.Using(typeof(OtherDummy)).ParseFor("((OtherDummy)SingleNavigation).TrueProperty")(dummy), Is.True); 26 | } 27 | 28 | [Test] 29 | public void ExpressionParser_UsingInterface_WithValidInput_ShouldPass() 30 | { 31 | Assert.That(ExpressionParser.Using(typeof(IOtherDummy)).ParseFor("((IOtherDummy)SingleNavigation).TrueProperty")(dummy), Is.True); 32 | } 33 | 34 | [Test] 35 | public void ExpressionParser_UsingSingleTypeWithAlias_WithValidInput_ShouldPass() 36 | { 37 | Assert.That(ExpressionParser.Using(typeof(OtherDummy), "other").ParseFor("((other)SingleNavigation).TrueProperty")(dummy), Is.True); 38 | } 39 | 40 | [Test] 41 | public void ExpressionParser_UsingTypeMap_WithValidInput_ShouldPass() 42 | { 43 | var types = new Dictionary 44 | { 45 | { typeof(OtherDummy), "other" }, 46 | { typeof(SomeOther), "navigation" } 47 | }; 48 | Assert.That(ExpressionParser.Using(types).ParseFor("((other)SingleNavigation).TrueProperty")(dummy), Is.True); 49 | } 50 | 51 | [Test] 52 | public void ExpressionParser_UsingTypeMap_WithNullAlias_ShouldThrow() 53 | { 54 | var types = new Dictionary 55 | { 56 | { typeof(OtherDummy), "other" }, 57 | { typeof(SomeOther), null } 58 | }; 59 | Assert.That(() => ExpressionParser.Using(types).Parse("Anything")(), Throws.Exception); 60 | } 61 | 62 | [Test] 63 | public void ExpressionParser_UsingListOfTypes_WithValidInput_ShouldPass() 64 | { 65 | Assert.That(ExpressionParser.Using(new [] { typeof(OtherDummy), typeof(SomeOther) }).ParseFor("((OtherDummy)SingleNavigation).TrueProperty")(dummy), Is.True); 66 | } 67 | 68 | [Test] 69 | public void ExpressionParser_UsingLWithNullAlias_ShouldThrow() 70 | { 71 | } 72 | 73 | [Test] 74 | public void ExpressionParser_UsingLWithNullArgument_ShouldThrow() 75 | { 76 | Assert.Throws(() => ExpressionParser.Using((Type)null).Parse("Anything")()); 77 | Assert.Throws(() => ExpressionParser.Using((IEnumerable)null).Parse("Anything")()); 78 | Assert.Throws(() => ExpressionParser.Using((IDictionary)null).Parse("Anything")()); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /ExpressionParser.Tests/Extensions/EnumerableExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Linq; 4 | using ExpressionParser.Extensions; 5 | using NUnit.Framework; 6 | 7 | namespace ExpressionParser.Tests.Extensions 8 | { 9 | [TestFixture] 10 | [ExcludeFromCodeCoverage] 11 | public class EnumerableExtensionsTests 12 | { 13 | [Test] 14 | public void ToArray_ForValidCollection_ShouldPass() 15 | { 16 | var source = new List {1, 2, 3}; 17 | Assert.That(source.ToArray(i => i.ToString()), Is.EquivalentTo(new [] { "1", "2" , "3" })); 18 | } 19 | 20 | [Test] 21 | public void ToArray_ForEmptyCollection_ShouldPass() 22 | { 23 | var source = Enumerable.Empty(); 24 | Assert.That(source.ToArray(i => i.ToString()), Is.EquivalentTo(new string[] { })); 25 | } 26 | 27 | 28 | [Test] 29 | public void ToArray_ForNullCollection_ShouldPass() 30 | { 31 | IEnumerable source = null; 32 | // ReSharper disable once ExpressionIsAlwaysNull 33 | Assert.That(source.ToArray(i => i.ToString()), Is.EquivalentTo(new string[] { })); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /ExpressionParser.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("ExpressionParser.Tests")] 5 | [assembly: AssemblyDescription("")] 6 | [assembly: AssemblyConfiguration("")] 7 | [assembly: AssemblyCompany("Andre Vianna")] 8 | [assembly: AssemblyProduct("ExpressionParser.Tests")] 9 | [assembly: AssemblyCopyright("Copyright © 2018")] 10 | [assembly: AssemblyTrademark("")] 11 | [assembly: AssemblyCulture("")] 12 | 13 | [assembly: ComVisible(false)] 14 | 15 | [assembly: Guid("d47e6ecf-68cd-4204-9daa-b227fb4b41d8")] 16 | 17 | // [assembly: AssemblyVersion("1.0.*")] 18 | [assembly: AssemblyVersion("1.0.0.0")] 19 | [assembly: AssemblyFileVersion("1.0.0.0")] 20 | -------------------------------------------------------------------------------- /ExpressionParser.Tests/TestDoubles/IOtherDummy.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace ExpressionParser.Tests.TestDoubles 4 | { 5 | internal interface IOtherDummy 6 | { 7 | int IntProperty { get; set; } 8 | ICollection ManyNavigation { get; } 9 | string StringProperty { get; set; } 10 | bool TrueProperty { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /ExpressionParser.Tests/TestDoubles/ISomeDummy.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace ExpressionParser.Tests.TestDoubles 4 | { 5 | internal interface ISomeDummy 6 | { 7 | int[] ArrayProperty { get; } 8 | decimal DecimalProperty { get; set; } 9 | bool FalseProperty { get; set; } 10 | int IntProperty { get; set; } 11 | ICollection ManyNavigation { get; } 12 | string NullProperty { get; set; } 13 | OtherDummy SingleNavigation { get; set; } 14 | string StringProperty { get; set; } 15 | bool TrueProperty { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /ExpressionParser.Tests/TestDoubles/OtherDummy.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace ExpressionParser.Tests.TestDoubles 5 | { 6 | [ExcludeFromCodeCoverage] 7 | internal class OtherDummy : IOtherDummy 8 | { 9 | public bool TrueProperty { get; set; } = true; 10 | public int IntProperty { get; set; } = 7; 11 | public string StringProperty { get; set; } = "Nurse!"; 12 | public ICollection ManyNavigation { get; } = new List(); 13 | } 14 | } -------------------------------------------------------------------------------- /ExpressionParser.Tests/TestDoubles/SomeDummy.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace ExpressionParser.Tests.TestDoubles 5 | { 6 | [ExcludeFromCodeCoverage] 7 | internal class SomeDummy : ISomeDummy 8 | { 9 | public bool TrueProperty { get; set; } = true; 10 | public bool FalseProperty { get; set; } = false; 11 | public int IntProperty { get; set; } = 42; 12 | public decimal DecimalProperty { get; set; } = 6.283184m; 13 | public string StringProperty { get; set; } = "Hello"; 14 | public string NullProperty { get; set; } = null; 15 | public int[] ArrayProperty { get; } = { 1, 2, 3, 4 }; 16 | 17 | public OtherDummy SingleNavigation { get; set; } = new OtherDummy(); 18 | public ICollection ManyNavigation { get; } = new List(); 19 | } 20 | } -------------------------------------------------------------------------------- /ExpressionParser.Tests/TestDoubles/SomeOther.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace ExpressionParser.Tests.TestDoubles 4 | { 5 | [ExcludeFromCodeCoverage] 6 | internal class SomeOther 7 | { 8 | public SomeDummy Some { get; set; } 9 | public OtherDummy Other { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /ExpressionParser.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ExpressionParser.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2024 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8B7DDA91-008F-4AF8-BB53-6A6DC920274B}" 7 | ProjectSection(SolutionItems) = preProject 8 | .travis.yml = .travis.yml 9 | CONTRIBUTING.md = CONTRIBUTING.md 10 | FEATURES.md = FEATURES.md 11 | GeneratePackage.cmd = GeneratePackage.cmd 12 | LICENSE.md = LICENSE.md 13 | README.md = README.md 14 | EndProjectSection 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressionParser", "ExpressionParser\ExpressionParser.csproj", "{869CDC74-F58F-4600-8636-D0B0CCCD4416}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressionParser.Tests", "ExpressionParser.Tests\ExpressionParser.Tests.csproj", "{D47E6ECF-68CD-4204-9DAA-B227FB4B41D8}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {869CDC74-F58F-4600-8636-D0B0CCCD4416}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {869CDC74-F58F-4600-8636-D0B0CCCD4416}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {869CDC74-F58F-4600-8636-D0B0CCCD4416}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {869CDC74-F58F-4600-8636-D0B0CCCD4416}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {D47E6ECF-68CD-4204-9DAA-B227FB4B41D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {D47E6ECF-68CD-4204-9DAA-B227FB4B41D8}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {D47E6ECF-68CD-4204-9DAA-B227FB4B41D8}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {D47E6ECF-68CD-4204-9DAA-B227FB4B41D8}.Release|Any CPU.Build.0 = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(ExtensibilityGlobals) = postSolution 39 | SolutionGuid = {ADA7D122-23BE-4B93-948E-DABB89299F90} 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /ExpressionParser/Engine/Builder.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Linq.Expressions; 3 | using ExpressionParser.Model; 4 | using ExpressionParser.Model.Nodes; 5 | 6 | namespace ExpressionParser.Engine 7 | { 8 | internal static class Builder 9 | { 10 | internal static LambdaExpression BuildExpression(TokenList tokens) 11 | { 12 | var root = BuildTree(tokens); 13 | var body = root.BuildExpression(); 14 | return Expression.Lambda(body); 15 | } 16 | 17 | internal static LambdaExpression BuildExpressionFor(TokenList tokens, string parameterName) 18 | { 19 | var root = BuildTree(tokens); 20 | var parameterExpression = parameterName == null ? Expression.Parameter(typeof(TInput)) : Expression.Parameter(typeof(TInput), parameterName); 21 | var body = root.BuildExpression(parameterExpression); 22 | return Expression.Lambda(body, parameterExpression); 23 | } 24 | 25 | private static Node BuildTree(TokenList tokens) 26 | { 27 | var nodes = new NodeStack(); 28 | while (tokens.Any() && !(tokens.Current.EndsExpressionOrParameters || tokens.Current.IsParameterSeparator || tokens.Current.EndsIndex)) 29 | { 30 | if (tokens.Current.StartsExpressionOrParameters && nodes.LastAdded is MethodNode method) { 31 | tokens.MoveNext(); 32 | ProcessParameters(tokens, method); 33 | } else if (tokens.Current.StartsExpressionOrParameters) { 34 | tokens.MoveNext(); 35 | ProcessExpression(tokens, nodes); 36 | } else if (tokens.Current.StartsIndex) { 37 | tokens.MoveNext(); 38 | ProcessIndex(tokens, nodes); 39 | } else { 40 | nodes.Add(tokens.Current.CreateNode()); 41 | } 42 | 43 | tokens.MoveNext(); 44 | } 45 | return nodes.Pop(); 46 | } 47 | 48 | private static void ProcessParameters(TokenList tokens, MethodNode methodNode) 49 | { 50 | while (!tokens.Current.EndsExpressionOrParameters) { 51 | var childNode = BuildTree(tokens); 52 | methodNode.Parameters.Add(childNode); 53 | if (tokens.Current.IsParameterSeparator) { 54 | tokens.MoveNext(); 55 | } 56 | } 57 | } 58 | 59 | private static void ProcessExpression(TokenList tokens, NodeStack nodes) 60 | { 61 | var childNode = BuildTree(tokens); 62 | childNode.KickPrecedenceUp(); 63 | nodes.Add(childNode); 64 | } 65 | 66 | private static void ProcessIndex(TokenList tokens, NodeStack nodes) 67 | { 68 | nodes.Add(new ArrayIndexNode()); 69 | var childNode = BuildTree(tokens); 70 | nodes.Add(childNode); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /ExpressionParser/Engine/Reader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | using ExpressionParser.Model; 6 | using ExpressionParser.Model.Tokens; 7 | 8 | namespace ExpressionParser.Engine 9 | { 10 | internal class Reader 11 | { 12 | private readonly TokenList result = new TokenList(); 13 | private int characterPosition; 14 | private static readonly IDictionary availableTypes = new Dictionary(Keywords.BuiltInTypes); 15 | 16 | internal static void AddTypeMap(string alias, Type type) => availableTypes[alias ?? type?.Name ?? throw new ArgumentNullException(nameof(type))] = type; 17 | 18 | public TokenList ReadFrom(string input) 19 | { 20 | for (characterPosition = 0; characterPosition < input.Length;) 21 | ProcessCharacter(input); 22 | return result; 23 | } 24 | 25 | private void ProcessCharacter(string input) 26 | { 27 | if (FindValidToken(input)) return; 28 | throw new ArgumentException($"Invalid token at position {characterPosition + 1}.", nameof(input)); 29 | } 30 | 31 | private bool FindValidToken(string input) 32 | { 33 | return FindWhiteSpace(input) || FindChar(input) || FindString(input) || FindDecimal(input) || FindInteger(input) || FindToken(input) || FindCandidate(input); 34 | } 35 | 36 | private bool FindWhiteSpace(string input) 37 | { 38 | return TryCreateToken(input.Substring(characterPosition), @"^\s+", a => null); 39 | } 40 | 41 | private bool FindChar(string input) 42 | { 43 | return TryCreateToken(input.Substring(characterPosition), @"^('[^\\']'|'\\[\\'trn]')", a => new LiteralToken(ConvertToChar(a.Substring(1, a.Length - 2)))); 44 | } 45 | 46 | private static char ConvertToChar(string source) 47 | { 48 | switch (source) 49 | { 50 | case @"\t": return '\t'; 51 | case @"\r": return '\r'; 52 | case @"\n": return '\n'; 53 | case @"\'": return '\''; 54 | case @"\\": return '\\'; 55 | default: return source[0]; 56 | } 57 | } 58 | 59 | private bool FindString(string input) 60 | { 61 | return TryCreateToken(input.Substring(characterPosition), @"^""[^""]*""", a => new LiteralToken(a.Trim('"'))); 62 | } 63 | 64 | private bool FindDecimal(string input) 65 | { 66 | return TryCreateToken(input.Substring(characterPosition), @"^((\d*\.\d+)|(\d+\.\d*))", a => new LiteralToken(Convert.ToDecimal(a))); 67 | } 68 | private bool FindInteger(string input) 69 | { 70 | return TryCreateToken(input.Substring(characterPosition), @"^\d+", a => new LiteralToken(Convert.ToInt32(a))); 71 | } 72 | 73 | private bool FindCandidate(string input) 74 | { 75 | var match = Regex.Match(input.Substring(characterPosition), @"^[\w]*", RegexOptions.IgnoreCase); 76 | var candidate = match.Value; 77 | return FindNull(candidate) || FindBoolean(candidate) || FindNamedOperator(candidate) || FindType(candidate) || FindName(candidate); 78 | } 79 | 80 | private bool FindNull(string candidate) 81 | { 82 | return TryCreateToken(candidate, @"^null$", a => new LiteralToken(null)); 83 | } 84 | 85 | private bool FindBoolean(string candidate) 86 | { 87 | return TryCreateToken(candidate, @"^(true|false)$", a => new LiteralToken(Convert.ToBoolean(a))); 88 | } 89 | 90 | private bool FindNamedOperator(string candidate) 91 | { 92 | return TryCreateToken(candidate, @"^(is|as)$", a => new SymbolToken(a)); 93 | } 94 | 95 | private bool FindType(string candidate) 96 | { 97 | if (!availableTypes.TryGetValue(candidate, out var type)) return false; 98 | result.Add(new TypeToken(type, "Type")); 99 | characterPosition += candidate.Length; 100 | return true; 101 | } 102 | 103 | private bool FindName(string candidate) 104 | { 105 | return TryCreateToken(candidate, @"^[a-zA-Z_][\w]*$", a => new NameToken(a, "Property")); 106 | } 107 | 108 | private bool TryCreateToken(string source, string regex, Func creator) 109 | { 110 | var match = Regex.Match(source, regex, RegexOptions.IgnoreCase); 111 | if (!match.Success) return false; 112 | var token = creator(match.Value); 113 | if (token != null) result.Add(token); 114 | characterPosition += match.Length; 115 | return true; 116 | } 117 | 118 | private bool FindToken(string input) 119 | { 120 | var current = input[characterPosition]; 121 | var next = characterPosition < (input.Length - 1) ? input[characterPosition + 1] : (char?)null; 122 | return FindSupportedSymbol($"{current}{next}") || FindSupportedSymbol($"{current}"); 123 | } 124 | 125 | private bool FindSupportedSymbol(string token) 126 | { 127 | var candidates = TokenList.SupportedOperators.Keys.Where(i => i.Length == token.Length).ToArray(); 128 | var symbol = candidates.FirstOrDefault(s => s == token); 129 | if (symbol == null) return false; 130 | switch (symbol) { 131 | case "is": 132 | case "as": 133 | return false; 134 | case "+" when IsUnaryOperatorPattern(): 135 | result.Add(new SymbolToken("[+]")); 136 | break; 137 | case "-" when IsUnaryOperatorPattern(): 138 | result.Add(new SymbolToken("[-]")); 139 | break; 140 | case "(" when IsMethodtPattern(out var methodToken): 141 | methodToken.NodeType = "Method"; 142 | result.Add(new SymbolToken(token)); 143 | break; 144 | case ")" when IsTypeCastPattern(out var typeCastToken): 145 | typeCastToken.NodeType = "TypeCast"; 146 | result.RemoveAt(result.Count - 2); 147 | break; 148 | default: 149 | result.Add(new SymbolToken(token)); 150 | break; 151 | } 152 | characterPosition += token.Length; 153 | return true; 154 | } 155 | 156 | private bool IsUnaryOperatorPattern() 157 | { 158 | return !result.Any() || (result.TokenAt(result.Count - 1) is SymbolToken); 159 | } 160 | 161 | private bool IsTypeCastPattern(out TypeToken token) { 162 | token = (result.TokenAt(result.Count - 1) is TypeToken candidate 163 | && result.TokenAt(result.Count - 2) is SymbolToken previousSymbol 164 | && previousSymbol.Symbol == "(") ? candidate : null; 165 | return token != null; 166 | } 167 | 168 | private bool IsMethodtPattern(out NameToken token) 169 | { 170 | token = (result.TokenAt(result.Count - 1) is NameToken candidate) ? candidate : null; 171 | return token != null; 172 | } 173 | } 174 | } -------------------------------------------------------------------------------- /ExpressionParser/ExpressionParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ExpressionParser 5 | { 6 | public static class ExpressionParser 7 | { 8 | public static Delegate Parse(string input) 9 | { 10 | var parser = new ExpressionParserImplementation(); 11 | return parser.Parse(input); 12 | } 13 | 14 | public static Delegate ParseFor(string input, string parameterName = null) 15 | { 16 | var parser = new ExpressionParserImplementation(); 17 | return parser.Using(new[] { typeof(TInput) }).ParseFor(input, parameterName); 18 | } 19 | 20 | 21 | public static Func Parse(string input) 22 | { 23 | var parser = new ExpressionParserImplementation(); 24 | return parser.Parse(input); 25 | } 26 | 27 | public static Func ParseFor(string input, string parameterName = null) 28 | { 29 | var parser = new ExpressionParserImplementation(); 30 | return parser.Using(new [] { typeof(TInput), typeof(TOutput) }).ParseFor(input, parameterName); 31 | } 32 | 33 | 34 | public static IExpressionParser Using(Type type, string alias = null) 35 | { 36 | var parser = new ExpressionParserImplementation(); 37 | return parser.Using(type, alias); 38 | } 39 | 40 | public static IExpressionParser Using(IEnumerable types) 41 | { 42 | var parser = new ExpressionParserImplementation(); 43 | return parser.Using(types); 44 | } 45 | 46 | public static IExpressionParser Using(IDictionary typeMap) 47 | { 48 | var parser = new ExpressionParserImplementation(); 49 | return parser.Using(typeMap); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /ExpressionParser/ExpressionParser.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {869CDC74-F58F-4600-8636-D0B0CCCD4416} 8 | Library 9 | Properties 10 | ExpressionParser 11 | ExpressionParser 12 | v4.5.1 13 | 512 14 | SAK 15 | SAK 16 | SAK 17 | SAK 18 | 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | latest 29 | false 30 | 31 | 32 | pdbonly 33 | true 34 | bin\Release\ 35 | TRACE 36 | prompt 37 | 4 38 | latest 39 | false 40 | 41 | 42 | true 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /ExpressionParser/ExpressionParser.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CSharp.ExpressionParser 5 | $version$ 6 | $title$ 7 | Andre Vianna 8 | Andre Vianna 9 | https://raw.githubusercontent.com/AndreVianna/ExpressionParser/master/LICENSE.md 10 | https://github.com/AndreVianna/ExpressionParser 11 | https://raw.githubusercontent.com/AndreVianna/ExpressionParser/master/Icon256.png 12 | false 13 | $description$ 14 | 15 | 1.2.2 - More code clean up and bug corrections. 16 | 1.2.1 - Code clean up and small improvements. 17 | 1.2.0 - Added support to 'is' and 'as' operators. 18 | 1.1.0 - Added support to type cast. 19 | - Include support to default System type (see documentation). 20 | - Include "Using" methods to reference additional type. 21 | 1.0.1.* - Initial package; Compatible with .Net Framework 4.5.1. 22 | 23 | Copyright 2018 24 | Parser C# Linq Expression Dynamic 25 | 26 | -------------------------------------------------------------------------------- /ExpressionParser/ExpressionParser.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreVianna/ExpressionParser/b06492fffa1b36b6adb40f68e3d14d329d99b16e/ExpressionParser/ExpressionParser.pfx -------------------------------------------------------------------------------- /ExpressionParser/ExpressionParserImplementation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using ExpressionParser.Engine; 4 | 5 | namespace ExpressionParser 6 | { 7 | internal class ExpressionParserImplementation : IExpressionParser 8 | { 9 | private readonly Reader reader = new Reader(); 10 | 11 | public Delegate Parse(string input) 12 | { 13 | var tokens = reader.ReadFrom(input); 14 | var expression = Builder.BuildExpression(tokens); 15 | return expression.Compile(); 16 | } 17 | 18 | public Func Parse(string input) 19 | { 20 | return (Func)Parse(input); 21 | } 22 | 23 | public Delegate ParseFor(string input, string parameterName) 24 | { 25 | var tokens = reader.ReadFrom(input); 26 | var expression = Builder.BuildExpressionFor(tokens, parameterName); 27 | return expression.Compile(); 28 | } 29 | 30 | public Func ParseFor(string input, string parameterName) 31 | { 32 | return (Func)ParseFor(input, parameterName); 33 | } 34 | 35 | public IExpressionParser Using(Type type, string alias) 36 | { 37 | Reader.AddTypeMap(alias, type); 38 | return this; 39 | } 40 | 41 | public IExpressionParser Using(IEnumerable types) 42 | { 43 | if (types == null) throw new ArgumentNullException(nameof(types)); 44 | foreach (var type in types) 45 | Reader.AddTypeMap(type.Name, type); 46 | return this; 47 | } 48 | 49 | public IExpressionParser Using(IDictionary typeMaps) 50 | { 51 | if (typeMaps == null) throw new ArgumentNullException(nameof(typeMaps)); 52 | foreach (var typeMap in typeMaps) 53 | Reader.AddTypeMap(typeMap.Value, typeMap.Key); 54 | return this; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /ExpressionParser/Extensions/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace ExpressionParser.Extensions 6 | { 7 | public static class EnumerableExtensions 8 | { 9 | public static TOutput[] ToArray(this IEnumerable source, Func select) 10 | { 11 | return source?.Select(select).ToArray() ?? new TOutput[] { }; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ExpressionParser/Extensions/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ExpressionParser.Extensions 4 | { 5 | public static class TypeExtensions 6 | { 7 | public static bool IsNullable(this Type source) 8 | { 9 | return source == typeof(string) || Nullable.GetUnderlyingType(source) != null; 10 | } 11 | 12 | public static Type MakeNullableType(this Type source) 13 | { 14 | return typeof(Nullable<>).MakeGenericType(source); 15 | } 16 | 17 | public static object GetDefaultValue(this Type source) 18 | { 19 | return !source.IsNullable() ? Activator.CreateInstance(source) : null; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /ExpressionParser/IExpressionParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ExpressionParser 5 | { 6 | public interface IExpressionParser 7 | { 8 | Delegate Parse(string input); 9 | 10 | Func Parse(string input); 11 | 12 | Delegate ParseFor(string input, string parameterName = null); 13 | 14 | Func ParseFor(string input, string parameterName = null); 15 | 16 | IExpressionParser Using(Type type, string alias = null); 17 | IExpressionParser Using(IEnumerable types); 18 | IExpressionParser Using(IDictionary typeMaps); 19 | } 20 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Keywords.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ExpressionParser.Model 5 | { 6 | internal static class Keywords 7 | { 8 | internal static readonly IDictionary BuiltInTypes = new Dictionary { 9 | { "bool", typeof(bool) }, 10 | { "byte", typeof(byte) }, 11 | { "sbyte", typeof(sbyte) }, 12 | { "char", typeof(char) }, 13 | { "decimal", typeof(decimal) }, 14 | { "double", typeof(double) }, 15 | { "float", typeof(float) }, 16 | { "int", typeof(int) }, 17 | { "uint", typeof(uint) }, 18 | { "long", typeof(long) }, 19 | { "ulong", typeof(ulong) }, 20 | { "object", typeof(object) }, 21 | { "short", typeof(short) }, 22 | { "ushort", typeof(ushort) }, 23 | { "string", typeof(string) }, 24 | { "Boolean", typeof(bool) }, 25 | { "Byte", typeof(byte) }, 26 | { "SByte", typeof(sbyte) }, 27 | { "Char", typeof(char) }, 28 | { "Decimal", typeof(decimal) }, 29 | { "Double", typeof(double) }, 30 | { "Single", typeof(float) }, 31 | { "Int32", typeof(int) }, 32 | { "UInt32", typeof(uint) }, 33 | { "Int64", typeof(long) }, 34 | { "UInt64", typeof(ulong) }, 35 | { "Object", typeof(object) }, 36 | { "Int16", typeof(short) }, 37 | { "UInt16", typeof(ushort) }, 38 | { "String", typeof(string) } 39 | }; 40 | 41 | internal static readonly IEnumerable TypeDefinition = new[] 42 | { 43 | "interface", 44 | "class", 45 | "enum", 46 | "struct" 47 | }; 48 | 49 | internal static readonly IEnumerable Query = new[] 50 | { 51 | "from", 52 | "where", 53 | "select", 54 | "group", 55 | "into", 56 | "orderby", 57 | "join", 58 | "let", 59 | "in", 60 | "on", 61 | "equals", 62 | "by", 63 | "ascending", 64 | "descending" 65 | }; 66 | 67 | internal static readonly IEnumerable Contextual = new[] 68 | { 69 | "add", 70 | "async", 71 | "await", 72 | "dynamic", 73 | "get", 74 | "global", 75 | "partial", 76 | "remove", 77 | "set", 78 | "value", 79 | "var", 80 | "when", 81 | "where", 82 | "yield" 83 | }; 84 | 85 | internal static readonly IEnumerable MemberAccess = new[] 86 | { 87 | "this", 88 | "base" 89 | }; 90 | 91 | internal static readonly IEnumerable Conversion = new[] 92 | { 93 | "explicit", 94 | "implicit", 95 | "operator" 96 | }; 97 | 98 | internal static readonly IEnumerable Operators = new[] 99 | { 100 | "as", 101 | "await", 102 | "is", 103 | "new", 104 | "nameof", 105 | "sizeof", 106 | "typeof", 107 | "stackalloc", 108 | "checked", 109 | "unchecked" 110 | }; 111 | 112 | internal static readonly IEnumerable Statements = new[] 113 | { 114 | "if", 115 | "else", 116 | "switch", 117 | "case", 118 | "default", 119 | "do", 120 | "for", 121 | "foreach", 122 | "in", 123 | "while", 124 | "break", 125 | "continue", 126 | "goto", 127 | "return", 128 | "throw", 129 | "try", 130 | "catch", 131 | "finally", 132 | "checked", 133 | "unchecked", 134 | "fixed", 135 | "lock" 136 | }; 137 | 138 | internal static readonly IEnumerable MethodDefinition = new[] 139 | { 140 | "delegate", 141 | "void", 142 | "params", 143 | "ref", 144 | "out", 145 | "function", 146 | "return" 147 | }; 148 | 149 | internal static readonly IEnumerable Namespace = new[] 150 | { 151 | "namespace", 152 | "using", 153 | "extern" 154 | }; 155 | 156 | internal static readonly IEnumerable Modifiers = new[] 157 | { 158 | "public", 159 | "protected", 160 | "internal", 161 | "private", 162 | "abstract", 163 | "async", 164 | "const", 165 | "event", 166 | "extern", 167 | "new", 168 | "override", 169 | "partial", 170 | "readonly", 171 | "sealed", 172 | "static", 173 | "unsafe", 174 | "virtual", 175 | "volatile", 176 | "in", 177 | "out" 178 | }; 179 | 180 | internal static readonly IEnumerable Literals = new[] 181 | { 182 | "null", 183 | "false", 184 | "true", 185 | "default" 186 | }; 187 | } 188 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/NodeStack.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using ExpressionParser.Model.Nodes; 5 | 6 | namespace ExpressionParser.Model 7 | { 8 | internal class NodeStack : Stack 9 | { 10 | internal Node LastAdded; 11 | 12 | internal void Add(Node node) 13 | { 14 | if (!this.Any()) 15 | Push(node); 16 | else switch (node) { 17 | case BinaryNode binaryNode when binaryNode.IsClosed: 18 | AttachNodeToRoot(node); 19 | break; 20 | case BinaryNode binaryNode when Peek() is BinaryNode root && root.Precedence <= binaryNode.Precedence: 21 | AttachRootToNodeLeft(binaryNode); 22 | break; 23 | case BinaryNode binaryNode when Peek() is BinaryNode root: 24 | MoveRootRightToNodeLeft(root, binaryNode); 25 | AttachNodeToRootRight(root, binaryNode); 26 | break; 27 | case BinaryNode binaryNode: 28 | AttachRootToNodeLeft(binaryNode); 29 | break; 30 | default: 31 | AttachNodeToRoot(node); 32 | break; 33 | } 34 | LastAdded = node; 35 | } 36 | 37 | private void AttachNodeToRoot(Node node) 38 | { 39 | if (!Peek().TryAddNode(node)) 40 | throw new InvalidOperationException($"Error adding '{node.GetType().Name}' to '{Peek().GetType().Name}'."); 41 | } 42 | 43 | private static void MoveRootRightToNodeLeft(BinaryNode root, BinaryNode node) 44 | { 45 | node.Left = node.Left ?? root.Right; 46 | } 47 | 48 | private static void AttachNodeToRootRight(BinaryNode root, Node node) 49 | { 50 | root.Right = node; 51 | } 52 | 53 | private void AttachRootToNodeLeft(BinaryNode node) 54 | { 55 | node.Left = Pop(); 56 | Push(node); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/AddNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal class AddNode : BinaryNode 6 | { 7 | internal AddNode() : base(4) { } 8 | 9 | internal override Expression BuildExpression(Expression callerExpression = null) 10 | { 11 | return Expression.Add(Left.BuildExpression(callerExpression), Right.BuildExpression(callerExpression)); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/AndNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal class AndNode : BinaryNode 6 | { 7 | internal AndNode() : base(11) { } 8 | internal override Expression BuildExpression(Expression callerExpression = null) 9 | { 10 | return Expression.And(Left.BuildExpression(callerExpression), Right.BuildExpression(callerExpression)); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/ArrayIndexNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal class ArrayIndexNode : BinaryNode 6 | { 7 | internal ArrayIndexNode() : base(0) { } 8 | 9 | internal override Expression BuildExpression(Expression callerExpression = null) 10 | { 11 | var left = Left.BuildExpression(callerExpression); 12 | return Expression.MakeIndex(left, left.Type.GetProperty("Item"), new [] { Right.BuildExpression(callerExpression) }); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/BinaryNode.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionParser.Model.Nodes 2 | { 3 | internal abstract class BinaryNode : OperationNode 4 | { 5 | protected BinaryNode(int precedence) : base(precedence) { } 6 | 7 | internal Node Left { get; set; } 8 | internal Node Right { get; set; } 9 | 10 | internal override bool IsClosed => (Left?.IsClosed ?? false) && Right.IsClosed; 11 | 12 | internal override bool TryAddNode(Node node) 13 | { 14 | if (Right != null) return Right.TryAddNode(node); 15 | Right = node; 16 | return true; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/CoalesceNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal class CoalesceNode : BinaryNode 6 | { 7 | internal CoalesceNode() : base(13) { } 8 | 9 | internal override Expression BuildExpression(Expression callerExpression = null) 10 | { 11 | var left = Left.BuildExpression(callerExpression); 12 | var right = Right.BuildExpression(callerExpression); 13 | if (left is ConstantExpression leftValue && leftValue.Value == null) left = Expression.Convert(left, right.Type); 14 | return Expression.Coalesce(left, right); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/DivideNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal class DivideNode : BinaryNode 6 | { 7 | internal DivideNode() : base(3) { } 8 | 9 | internal override Expression BuildExpression(Expression callerExpression = null) 10 | { 11 | return Expression.Divide(Left.BuildExpression(callerExpression), Right.BuildExpression(callerExpression)); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/DotNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal class DotNode : BinaryNode 6 | { 7 | internal DotNode() : base(0) { } 8 | 9 | internal override Expression BuildExpression(Expression callerExpression = null) 10 | { 11 | return Right.BuildExpression(Left.BuildExpression(callerExpression)); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/EqualNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal class EqualNode : BinaryNode 6 | { 7 | internal EqualNode() : base(7) { } 8 | 9 | internal override Expression BuildExpression(Expression callerExpression = null) 10 | { 11 | return Expression.Equal(Left.BuildExpression(callerExpression), Right.BuildExpression(callerExpression)); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/GreaterNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal class GreaterNode : BinaryNode 6 | { 7 | internal GreaterNode() : base(6) { } 8 | 9 | internal override Expression BuildExpression(Expression callerExpression = null) 10 | { 11 | return Expression.GreaterThan(Left.BuildExpression(callerExpression), Right.BuildExpression(callerExpression)); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/GreaterOrEqualNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal class GreaterOrEqualNode : BinaryNode 6 | { 7 | internal GreaterOrEqualNode() : base(6) { } 8 | 9 | internal override Expression BuildExpression(Expression callerExpression = null) 10 | { 11 | return Expression.GreaterThanOrEqual(Left.BuildExpression(callerExpression), Right.BuildExpression(callerExpression)); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/IdentifierNode.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionParser.Model.Nodes 2 | { 3 | internal abstract class IdentifierNode : Node 4 | { 5 | protected IdentifierNode(string name, int precedence) : base(precedence) => Name = name; 6 | 7 | internal string Name { get; } 8 | } 9 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/LambdaNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq.Expressions; 4 | 5 | namespace ExpressionParser.Model.Nodes 6 | { 7 | internal class LambdaNode : BinaryNode 8 | { 9 | internal LambdaNode() : base(14) { } 10 | 11 | internal override Expression BuildExpression(Expression callerExpression = null) 12 | { 13 | if (callerExpression == null) throw new ArgumentNullException(nameof(callerExpression)); 14 | var parameterName = ((IdentifierNode)Left).Name; 15 | var parameterType = GetParameterType(); 16 | Debug.Assert(parameterType != null); 17 | var parameterExpression = Expression.Parameter(parameterType, parameterName); 18 | return Expression.Lambda(Right.BuildExpression(parameterExpression), parameterExpression); 19 | 20 | Type GetParameterType() 21 | { 22 | return callerExpression.Type == typeof(string) ? typeof(char) : 23 | callerExpression.Type.IsArray ? callerExpression.Type.GetElementType() : 24 | callerExpression.Type.GetGenericArguments()[0]; 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/LessNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal class LessNode : BinaryNode 6 | { 7 | internal LessNode() : base(6) { } 8 | 9 | internal override Expression BuildExpression(Expression callerExpression = null) 10 | { 11 | return Expression.LessThan(Left.BuildExpression(callerExpression), Right.BuildExpression(callerExpression)); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/LessOrEqualNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal class LessOrEqualNode : BinaryNode 6 | { 7 | internal LessOrEqualNode() : base(6) { } 8 | 9 | internal override Expression BuildExpression(Expression callerExpression = null) 10 | { 11 | return Expression.LessThanOrEqual(Left.BuildExpression(callerExpression), Right.BuildExpression(callerExpression)); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/LiteralNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal abstract class LiteralNode : Node 6 | { 7 | protected LiteralNode() : base(99) { } 8 | } 9 | 10 | internal class LiteralNode : LiteralNode 11 | { 12 | internal LiteralNode(T value) => Value = value; 13 | 14 | internal T Value { get; } 15 | 16 | internal override Expression BuildExpression(Expression callerExpression = null) => Expression.Constant(Value); 17 | } 18 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/MethodNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using ExpressionParser.Extensions; 7 | 8 | namespace ExpressionParser.Model.Nodes 9 | { 10 | internal class MethodNode : IdentifierNode 11 | { 12 | private MethodInfo methodInfo; 13 | private bool isExtensionMethod; 14 | private Type callerType; 15 | private IList arguments; 16 | 17 | internal MethodNode(string name) : base(name, 1) { } 18 | 19 | internal IList Parameters { get; } = new List(); 20 | 21 | internal override Expression BuildExpression(Expression callerExpression = null) 22 | { 23 | if (callerExpression == null) throw new InvalidOperationException($"Invalid method '{Name}'"); 24 | callerType = callerExpression.Type; 25 | GetArguments(callerExpression); 26 | GetMethodInfo(callerExpression); 27 | if (isExtensionMethod) UpdateExtensionMethodInfo(); 28 | return GetCallExpression(callerExpression); 29 | } 30 | 31 | private void GetArguments(Expression callerExpression = null) 32 | { 33 | arguments = Parameters.Select(p => p.BuildExpression(callerExpression)).ToList(); 34 | } 35 | 36 | private void GetMethodInfo(Expression callerExpression = null) 37 | { 38 | methodInfo = GetInstanceMethod(Name) ?? GetExtensionMethod(Name, callerExpression); 39 | } 40 | 41 | private void UpdateExtensionMethodInfo() 42 | { 43 | var genericArgumentType = callerType == typeof(string) ? typeof(char) : (callerType.IsArray ? callerType.GetElementType() : callerType.GetGenericArguments()[0]); 44 | methodInfo = methodInfo.MakeGenericMethod(genericArgumentType); 45 | } 46 | 47 | private MethodInfo GetInstanceMethod(string name) 48 | { 49 | var candidates = callerType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy).Where(i => i.Name == name).ToArray(); 50 | return GetMethodInfoFromCandidates(candidates); 51 | } 52 | 53 | private MethodInfo GetExtensionMethod(string name, Expression callExpression) 54 | { 55 | arguments.Insert(0, callExpression); 56 | var candidates = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(i => i.Name == name).ToArray(); 57 | var method = GetMethodInfoFromCandidates(candidates); 58 | isExtensionMethod = method != null; 59 | return method; 60 | } 61 | 62 | private MethodInfo GetMethodInfoFromCandidates(IEnumerable candidates) 63 | { 64 | return (from candidate in candidates 65 | let parametersTypes = candidate.GetParameters().ToArray(p => p.ParameterType) 66 | where parametersTypes.Length == arguments.Count 67 | && parametersTypes.Zip(arguments, AreEquivalent).All(e => e) 68 | select candidate).FirstOrDefault(); 69 | } 70 | 71 | private static bool AreEquivalent(Type parameterType, Expression argument) 72 | { 73 | return argument.Type.Name == parameterType.Name || argument.Type.GetInterfaces().Any(i => i.Name.Equals(parameterType.Name)); 74 | } 75 | 76 | private Expression GetCallExpression(Expression parameter) 77 | { 78 | return isExtensionMethod ? Expression.Call(methodInfo, arguments) : Expression.Call(parameter, methodInfo, arguments); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/ModuloNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal class ModuloNode : BinaryNode 6 | { 7 | internal ModuloNode() : base(3) { } 8 | 9 | internal override Expression BuildExpression(Expression callerExpression = null) 10 | { 11 | return Expression.Modulo(Left.BuildExpression(callerExpression), Right.BuildExpression(callerExpression)); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/MultiplyNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal class MultiplyNode : BinaryNode 6 | { 7 | internal MultiplyNode() : base(3) { } 8 | 9 | internal override Expression BuildExpression(Expression callerExpression = null) 10 | { 11 | return Expression.Multiply(Left.BuildExpression(callerExpression), Right.BuildExpression(callerExpression)); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/NegateNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal class NegateNode : UnaryNode 6 | { 7 | internal NegateNode() : base(2) { } 8 | 9 | internal override Expression BuildExpression(Expression callerExpression = null) 10 | { 11 | return Expression.Negate(Child.BuildExpression(callerExpression)); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/Node.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal abstract class Node 6 | { 7 | protected Node(int precedence) => Precedence = precedence; 8 | 9 | internal int Precedence { get; set; } 10 | 11 | internal virtual bool IsClosed => true; 12 | 13 | internal abstract Expression BuildExpression(Expression callerExpression = null); 14 | 15 | internal void KickPrecedenceUp() => Precedence = 0; 16 | 17 | internal virtual bool TryAddNode(Node node) => false; 18 | } 19 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/NotEqualNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal class NotEqualNode : BinaryNode 6 | { 7 | internal NotEqualNode() : base(7) { } 8 | 9 | internal override Expression BuildExpression(Expression callerExpression = null) 10 | { 11 | return Expression.NotEqual(Left.BuildExpression(callerExpression), Right.BuildExpression(callerExpression)); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/NotNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal class NotNode : UnaryNode 6 | { 7 | internal NotNode() : base(2) { } 8 | 9 | internal override Expression BuildExpression(Expression callerExpression = null) 10 | { 11 | return Expression.Not(Child.BuildExpression(callerExpression)); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/NullPropagationNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using ExpressionParser.Extensions; 3 | 4 | namespace ExpressionParser.Model.Nodes 5 | { 6 | internal class NullPropagationNode : BinaryNode 7 | { 8 | internal NullPropagationNode() : base(0) { } 9 | 10 | internal override Expression BuildExpression(Expression callerExpression = null) 11 | { 12 | var left = Left.BuildExpression(callerExpression); 13 | var right = Right.BuildExpression(Left.BuildExpression(callerExpression)); 14 | if (!right.Type.IsNullable()) right = Expression.Convert(right, right.Type.MakeNullableType()); 15 | return Expression.Condition(Expression.Equal(left, Expression.Constant(null, left.Type)), Expression.Constant(null, right.Type), right); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/OperationNode.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionParser.Model.Nodes 2 | { 3 | internal abstract class OperationNode : Node 4 | { 5 | protected OperationNode(int precedence) : base(precedence) { } 6 | } 7 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/OrNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal class OrNode : BinaryNode 6 | { 7 | internal OrNode() : base(12) { } 8 | 9 | internal override Expression BuildExpression(Expression callerExpression = null) 10 | { 11 | return Expression.Or(Left.BuildExpression(callerExpression), Right.BuildExpression(callerExpression)); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/PropertyNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace ExpressionParser.Model.Nodes 5 | { 6 | internal class PropertyNode : IdentifierNode 7 | { 8 | internal PropertyNode(string name) : base(name, 99) { } 9 | 10 | internal override Expression BuildExpression(Expression callerExpression = null) 11 | { 12 | switch (callerExpression) 13 | { 14 | case null: throw new InvalidOperationException($"Unknow identifier '{Name}'."); 15 | case ParameterExpression parameterExpression when parameterExpression.Name == Name: return callerExpression; 16 | default: return Expression.PropertyOrField(callerExpression, Name); 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/SubtractNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal class SubtractNode : BinaryNode 6 | { 7 | internal SubtractNode() : base(4) { } 8 | 9 | internal override Expression BuildExpression(Expression callerExpression = null) 10 | { 11 | return Expression.Subtract(Left.BuildExpression(callerExpression), Right.BuildExpression(callerExpression)); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/TypeAsNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using ExpressionParser.Extensions; 3 | 4 | namespace ExpressionParser.Model.Nodes 5 | { 6 | internal class TypeAsNode : BinaryNode 7 | { 8 | internal TypeAsNode() : base(6) { } 9 | 10 | internal override Expression BuildExpression(Expression callerExpression = null) 11 | { 12 | var rightType = Right.BuildExpression(callerExpression).Type; 13 | if (!rightType.IsNullable()) rightType = rightType.MakeNullableType(); 14 | return Expression.TypeAs(Left.BuildExpression(callerExpression), rightType); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/TypeCastNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace ExpressionParser.Model.Nodes 5 | { 6 | internal class TypeCastNode : UnaryNode 7 | { 8 | internal TypeCastNode(Type type) : base(0) 9 | { 10 | Type = type; 11 | } 12 | 13 | internal Type Type { get; } 14 | 15 | internal override Expression BuildExpression(Expression callerExpression = null) 16 | { 17 | var child = Child.BuildExpression(callerExpression); 18 | return Type == child.Type ? child : Expression.ConvertChecked(child, Type); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/TypeIsNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal class TypeIsNode : BinaryNode 6 | { 7 | internal TypeIsNode() : base(6) { } 8 | 9 | internal override Expression BuildExpression(Expression callerExpression = null) 10 | { 11 | return Expression.TypeIs(Left.BuildExpression(callerExpression), Right.BuildExpression(callerExpression).Type); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/TypeNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using ExpressionParser.Extensions; 4 | 5 | namespace ExpressionParser.Model.Nodes 6 | { 7 | internal class TypeNode : IdentifierNode 8 | { 9 | private readonly Type type; 10 | 11 | internal TypeNode(Type type) : base(type.FullName, 99) 12 | { 13 | this.type = type; 14 | } 15 | 16 | internal override Expression BuildExpression(Expression callerExpression = null) 17 | { 18 | return Expression.Convert(Expression.Constant(type.GetDefaultValue()), type); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/UnaryNode.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionParser.Model.Nodes 2 | { 3 | internal abstract class UnaryNode : OperationNode 4 | { 5 | protected UnaryNode(int precedence) : base(precedence) { } 6 | 7 | internal Node Child { get; set; } 8 | 9 | internal override bool IsClosed => Child.IsClosed; 10 | 11 | internal override bool TryAddNode(Node node) 12 | { 13 | if (Child != null) return Child.TryAddNode(node); 14 | Child = node; 15 | return true; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Nodes/ValueNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExpressionParser.Model.Nodes 4 | { 5 | internal class ValueNode : UnaryNode 6 | { 7 | internal ValueNode() : base(2) { } 8 | 9 | internal override Expression BuildExpression(Expression callerExpression = null) 10 | { 11 | return Child.BuildExpression(callerExpression); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/TokenList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using ExpressionParser.Model.Nodes; 4 | using ExpressionParser.Model.Tokens; 5 | 6 | namespace ExpressionParser.Model 7 | { 8 | internal class TokenList : List 9 | { 10 | internal Token TokenAt(int position) => (position >= 0 && position < Count) ? this[position] : null; 11 | 12 | internal Token Current => this[0]; 13 | internal void MoveNext() => RemoveAt(0); 14 | 15 | public static readonly IReadOnlyDictionary> SupportedOperators = new Dictionary> 16 | { 17 | {"[+]", () => new ValueNode()}, 18 | {"[-]", () => new NegateNode()}, 19 | {"is", () => new TypeIsNode()}, 20 | {"as", () => new TypeAsNode()}, 21 | {"!=", () => new NotEqualNode()}, 22 | {"==", () => new EqualNode()}, 23 | {"=>", () => new LambdaNode()}, 24 | {">=", () => new GreaterOrEqualNode()}, 25 | {"<=", () => new LessOrEqualNode()}, 26 | {"&&", () => new AndNode()}, 27 | {"||", () => new OrNode()}, 28 | {"??", () => new CoalesceNode()}, 29 | {"?.", () => new NullPropagationNode()}, 30 | {"!", () => new NotNode()}, 31 | {">", () => new GreaterNode()}, 32 | {"<", () => new LessNode()}, 33 | {"+", () => new AddNode()}, 34 | {"-", () => new SubtractNode()}, 35 | {"*", () => new MultiplyNode()}, 36 | {"/", () => new DivideNode()}, 37 | {"%", () => new ModuloNode()}, 38 | {".", () => new DotNode()}, 39 | {"[", null}, 40 | {"]", null}, 41 | {",", null}, 42 | {"(", null}, 43 | {")", null} 44 | }; 45 | } 46 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Tokens/LiteralToken.cs: -------------------------------------------------------------------------------- 1 | using ExpressionParser.Model.Nodes; 2 | 3 | namespace ExpressionParser.Model.Tokens 4 | { 5 | internal class LiteralToken : Token 6 | { 7 | private readonly T value; 8 | 9 | internal LiteralToken(T value) => this.value = value; 10 | 11 | internal override Node CreateNode() => new LiteralNode(value); 12 | } 13 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Tokens/NameToken.cs: -------------------------------------------------------------------------------- 1 | using ExpressionParser.Model.Nodes; 2 | 3 | namespace ExpressionParser.Model.Tokens 4 | { 5 | internal class NameToken : Token 6 | { 7 | private readonly string name; 8 | 9 | internal NameToken(string name, string nodeType) 10 | { 11 | this.name = name; 12 | NodeType = nodeType; 13 | } 14 | public string NodeType { get; set; } 15 | 16 | internal override Node CreateNode() => NodeType == "Method" ? (Node) new MethodNode(name) : new PropertyNode(name); 17 | } 18 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Tokens/SymbolToken.cs: -------------------------------------------------------------------------------- 1 | using ExpressionParser.Model.Nodes; 2 | 3 | namespace ExpressionParser.Model.Tokens 4 | { 5 | internal class SymbolToken : Token 6 | { 7 | internal SymbolToken(string symbol) => Symbol = symbol; 8 | 9 | internal string Symbol { get; } 10 | 11 | internal override Node CreateNode() => TokenList.SupportedOperators[Symbol](); 12 | 13 | internal override bool StartsIndex => Symbol == "["; 14 | internal override bool EndsIndex => Symbol == "]"; 15 | internal override bool StartsExpressionOrParameters => Symbol == "("; 16 | internal override bool IsParameterSeparator => Symbol == ","; 17 | internal override bool EndsExpressionOrParameters => Symbol == ")"; 18 | } 19 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Tokens/Token.cs: -------------------------------------------------------------------------------- 1 | using ExpressionParser.Model.Nodes; 2 | 3 | namespace ExpressionParser.Model.Tokens 4 | { 5 | internal abstract class Token 6 | { 7 | internal abstract Node CreateNode(); 8 | 9 | internal virtual bool StartsIndex => false; 10 | internal virtual bool EndsIndex => false; 11 | internal virtual bool StartsExpressionOrParameters => false; 12 | internal virtual bool EndsExpressionOrParameters => false; 13 | internal virtual bool IsParameterSeparator => false; 14 | } 15 | } -------------------------------------------------------------------------------- /ExpressionParser/Model/Tokens/TypeToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ExpressionParser.Model.Nodes; 3 | 4 | namespace ExpressionParser.Model.Tokens 5 | { 6 | internal class TypeToken : Token 7 | { 8 | private readonly Type type; 9 | 10 | internal TypeToken(Type type, string nodeType) 11 | { 12 | NodeType = nodeType; 13 | this.type = type; 14 | } 15 | 16 | public string NodeType { get; set; } 17 | 18 | internal override Node CreateNode() => NodeType == "Type" ? (Node) new TypeNode(type) : new TypeCastNode(type); 19 | } 20 | } -------------------------------------------------------------------------------- /ExpressionParser/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("ExpressionParser")] 5 | [assembly: AssemblyDescription("A C# expression parser for a single line string input.")] 6 | [assembly: AssemblyConfiguration("")] 7 | [assembly: AssemblyCompany("Andre Vianna")] 8 | [assembly: AssemblyProduct("ExpressionParser")] 9 | [assembly: AssemblyCopyright("Copyright © 2018")] 10 | [assembly: AssemblyTrademark("")] 11 | [assembly: AssemblyCulture("")] 12 | [assembly: ComVisible(false)] 13 | [assembly: Guid("869cdc74-f58f-4600-8636-d0b0cccd4416")] 14 | [assembly: AssemblyVersion("1.2.2")] 15 | -------------------------------------------------------------------------------- /FEATURES.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreVianna/ExpressionParser/b06492fffa1b36b6adb40f68e3d14d329d99b16e/FEATURES.md -------------------------------------------------------------------------------- /GeneratePackage.cmd: -------------------------------------------------------------------------------- 1 | nuget pack ExpressionParser\ExpressionParser.csproj -Prop Configuration=Release -------------------------------------------------------------------------------- /Icon256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreVianna/ExpressionParser/b06492fffa1b36b6adb40f68e3d14d329d99b16e/Icon256.png -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Andre Vianna 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C# Expression Parser 2 | 3 | The project provides a simple expression parser that transforms a string into a valid C# expression representing a function. 4 | The function can be called with or without a parameter or as part of a LINQ query. 5 | 6 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/2df98ec122e842a2912e48274cf5104f)](https://www.codacy.com/app/AndreVianna/ExpressionParser?utm_source=github.com&utm_medium=referral&utm_content=AndreVianna/ExpressionParser&utm_campaign=Badge_Grade) 7 | [![Build Status](https://travis-ci.org/AndreVianna/ExpressionParser.svg?branch=master)](https://travis-ci.org/AndreVianna/ExpressionParser) 8 | 9 | ## Getting Started 10 | 11 | The library contains a static class with 4 methods for parsing: 12 | 13 | ```csharp 14 | Func Parse(string input) 15 | Delegate Parse(string input) 16 | Func ParseFor(string input) 17 | Delegate ParseFor(string input) 18 | ``` 19 | 20 | And 3 methods used to give support to external types: 21 | 22 | ```csharp 23 | IExpressionParser Using(Type type, string alias = null) 24 | IExpressionParser Using(IEnumerable types) 25 | IExpressionParser Using(IDictionary typeMap) 26 | ``` 27 | 28 | All methods are also exposed by the public interface `IExpressionParser`. 29 | 30 | 31 | ### Prerequisites 32 | 33 | There is no prerequisite to install and use the methods included in this library. 34 | 35 | ### Installing 36 | 37 | You can install the ExpressionParser by downloading it as a NuGet package: 38 | 39 | ``` 40 | Install-Package CSharp.ExpressionParser 41 | ``` 42 | 43 | After that, you can just use the call directly from your code. 44 | Here is a couple of usage examples: 45 | 46 | ```csharp 47 | var result1 = ExpressionParser.Parse("(3 + 2) * 3")(); //result1 should be an integer of value 15 48 | var result2 = ExpressionParser.ParseFor("Id == 23 && IsActive == true")(instance); //result2 should be a boolean that the value shoul depend on the instance provided as input 49 | ``` 50 | 51 | If you don't know the output of the result in advance you can use: 52 | ```csharp 53 | var expression = ExpressionParser.Parse(someStringToBeParsed); //expression will have a delegate returning an object. 54 | var result = expression.DynamicInvoke(); //result will have the result of the expression as an object. 55 | ``` 56 | Here are a few samples of expressions that will be accepted by the case above: 57 | ```csharp 58 | "2.0 / 5.0" ==> Expected result: 0.4 (decimal) 59 | "3 + 2 * 3" ==> Expected result: 9 (int) 60 | "1 >= 1" ==> Expected result: true (bool) 61 | ``` 62 | 63 | This is also supported when an input value is provided, but in this case, the type of the input has to be informed. 64 | Here is an example: 65 | 66 | ```csharp 67 | var expression = ExpressionParser.ParseFor(someStringToBeParsed); 68 | var result = expression.DynamicInvoke(instaceOfTypeSomeClass); 69 | ``` 70 | 71 | In order to support external types or interfaces you can use the `Using` method to add them. For example: 72 | 73 | ```csharp 74 | var result = ExpressionParser.Using(new { typeof(IPerson), type(IMovie) }).Parse("((IPerson)record.Person).Age > ((IMovie)record.Movie).AgeLimit")(record); 75 | ``` 76 | 77 | 78 | More examples can be found in the test project. 79 | 80 | ## Supported Operators and Types 81 | 82 | Here is a list of [supported operators](FEATURES.md). 83 | 84 | ## The Tests 85 | 86 | A NUnit 3 test project is provided in the solution. 87 | The tests currently provide 100% of code coverage, but they are not complete. 88 | We plan to include more positive test cases and many more negative test cases in the future commits. 89 | 90 | ## Contributing 91 | 92 | Please read [CONTRIBUTING](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. 93 | 94 | ## Versioning 95 | 96 | We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/your/project/tags). 97 | 98 | ## Authors 99 | 100 | * **Andre Vianna** - *Initial work* - [AndreVianna](https://github.com/AndreVianna) 101 | 102 | See also the list of [contributors](https://github.com/AndreVianna/ExpressionParser/graphs/contributors) who participated in this project. 103 | 104 | ## License 105 | 106 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 107 | 108 | --------------------------------------------------------------------------------