├── FEATURES.md
├── Icon256.png
├── GeneratePackage.cmd
├── ExpressionParser
├── ExpressionParser.pfx
├── Model
│ ├── Nodes
│ │ ├── OperationNode.cs
│ │ ├── IdentifierNode.cs
│ │ ├── ValueNode.cs
│ │ ├── NotNode.cs
│ │ ├── DotNode.cs
│ │ ├── NegateNode.cs
│ │ ├── AndNode.cs
│ │ ├── OrNode.cs
│ │ ├── AddNode.cs
│ │ ├── TypeIsNode.cs
│ │ ├── EqualNode.cs
│ │ ├── LessNode.cs
│ │ ├── DivideNode.cs
│ │ ├── ModuloNode.cs
│ │ ├── GreaterNode.cs
│ │ ├── MultiplyNode.cs
│ │ ├── NotEqualNode.cs
│ │ ├── SubtractNode.cs
│ │ ├── LessOrEqualNode.cs
│ │ ├── GreaterOrEqualNode.cs
│ │ ├── UnaryNode.cs
│ │ ├── LiteralNode.cs
│ │ ├── ArrayIndexNode.cs
│ │ ├── Node.cs
│ │ ├── TypeNode.cs
│ │ ├── TypeCastNode.cs
│ │ ├── BinaryNode.cs
│ │ ├── TypeAsNode.cs
│ │ ├── CoalesceNode.cs
│ │ ├── PropertyNode.cs
│ │ ├── NullPropagationNode.cs
│ │ ├── LambdaNode.cs
│ │ └── MethodNode.cs
│ ├── Tokens
│ │ ├── LiteralToken.cs
│ │ ├── Token.cs
│ │ ├── NameToken.cs
│ │ ├── TypeToken.cs
│ │ └── SymbolToken.cs
│ ├── TokenList.cs
│ ├── NodeStack.cs
│ └── Keywords.cs
├── Extensions
│ ├── EnumerableExtensions.cs
│ └── TypeExtensions.cs
├── IExpressionParser.cs
├── Properties
│ └── AssemblyInfo.cs
├── ExpressionParser.nuspec
├── ExpressionParser.cs
├── ExpressionParserImplementation.cs
├── Engine
│ ├── Builder.cs
│ └── Reader.cs
└── ExpressionParser.csproj
├── ExpressionParser.Tests
├── packages.config
├── TestDoubles
│ ├── SomeOther.cs
│ ├── IOtherDummy.cs
│ ├── OtherDummy.cs
│ ├── ISomeDummy.cs
│ └── SomeDummy.cs
├── Properties
│ └── AssemblyInfo.cs
├── Extensions
│ └── EnumerableExtensionsTests.cs
├── ExpressionParserUsingTests.cs
├── ExpressionParserParseTests.cs
├── ExpressionParser.Tests.csproj
└── ExpressionParserParseForTests.cs
├── .travis.yml
├── LICENSE.md
├── ExpressionParser.sln
├── CODE_OF_CONDUCT.md
├── README.md
├── CONTRIBUTING.md
└── .gitignore
/FEATURES.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndreVianna/ExpressionParser/HEAD/FEATURES.md
--------------------------------------------------------------------------------
/Icon256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndreVianna/ExpressionParser/HEAD/Icon256.png
--------------------------------------------------------------------------------
/GeneratePackage.cmd:
--------------------------------------------------------------------------------
1 | nuget pack ExpressionParser\ExpressionParser.csproj -Prop Configuration=Release
--------------------------------------------------------------------------------
/ExpressionParser/ExpressionParser.pfx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndreVianna/ExpressionParser/HEAD/ExpressionParser/ExpressionParser.pfx
--------------------------------------------------------------------------------
/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.Tests/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/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.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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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.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/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/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/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/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.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/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/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/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/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/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/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/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 | }
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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.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/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.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/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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.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.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/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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.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 |
--------------------------------------------------------------------------------
/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 | [](https://www.codacy.com/app/AndreVianna/ExpressionParser?utm_source=github.com&utm_medium=referral&utm_content=AndreVianna/ExpressionParser&utm_campaign=Badge_Grade)
7 | [](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 |
--------------------------------------------------------------------------------
/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/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.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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