├── .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