Skip to content

Commit 638b07a

Browse files
committed
Merge pull request #182 from JakeGinnivan/ExpressionParser
Expression parser
2 parents 596d78a + a0fb16f commit 638b07a

5 files changed

Lines changed: 59 additions & 225 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ PackageBuild/*
1616
Build/*
1717
TestResult.xml
1818
TestStack.BDDfy.sln.ide/graph
19+
_NCrunch_TestStack.BDDfy

TestStack.BDDfy/Processors/ScenarioExecutor.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Linq;
3+
using System.Linq.Expressions;
34
using System.Reflection;
45
using TestStack.BDDfy.Configuration;
56

@@ -28,8 +29,8 @@ public void InitializeScenario()
2829

2930
var possibleTargets = memberInfos
3031
.OfType<FieldInfo>()
31-
.Select(f => new StepArgument(f, _scenario.TestObject))
32-
.Union(memberInfos.OfType<PropertyInfo>().Select(m => new StepArgument(m, _scenario.TestObject)))
32+
.Select(f => new StepArgument(f.Name, f.FieldType, () => f.GetValue(_scenario.TestObject), o => f.SetValue(_scenario.TestObject, o)))
33+
.Union(memberInfos.OfType<PropertyInfo>().Select(m => new StepArgument(m.Name, m.PropertyType, () => m.GetValue(_scenario.TestObject, null), o => m.SetValue(_scenario.TestObject, o, null))))
3334
.Union(_scenario.Steps.SelectMany(s=>s.Arguments))
3435
.ToArray();
3536

Lines changed: 49 additions & 206 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Collections;
32
using System.Collections.Generic;
43
using System.Linq;
54
using System.Linq.Expressions;
@@ -20,231 +19,75 @@ public static IEnumerable<StepArgument> ExtractArguments<T>(this Expression<Acti
2019
if (methodCallExpression == null)
2120
throw new InvalidOperationException("Please provide a *method call* lambda expression.");
2221

23-
return ExtractArguments(methodCallExpression, value, false);
22+
return new ArgumentExtractorVisitor().ExtractArguments(expression, value);
2423
}
2524

2625
public static IEnumerable<StepArgument> ExtractArguments<T>(this Expression<Func<T, Task>> expression, T value)
2726
{
28-
var lambdaExpression = expression as LambdaExpression;
29-
if (lambdaExpression == null)
30-
throw new InvalidOperationException("Please provide a lambda expression.");
31-
32-
var methodCallExpression = lambdaExpression.Body as MethodCallExpression;
33-
if (methodCallExpression == null)
34-
throw new InvalidOperationException("Please provide a *method call* lambda expression.");
35-
36-
return ExtractArguments(methodCallExpression, value, false);
27+
return new ArgumentExtractorVisitor().ExtractArguments(expression, value);
3728
}
3829

39-
private static IEnumerable<StepArgument> ExtractArguments<T>(Expression expression, T value)
30+
private class ArgumentExtractorVisitor : ExpressionVisitor
4031
{
41-
if (expression == null || expression is ParameterExpression)
42-
return new StepArgument[0];
43-
44-
var memberExpression = expression as MemberExpression;
45-
if (memberExpression != null)
46-
return new[] { ExtractArgument(memberExpression, value) };
47-
48-
var constantExpression = expression as ConstantExpression;
49-
if (constantExpression != null)
50-
return ExtractArguments(constantExpression, value);
51-
52-
var newArrayExpression = expression as NewArrayExpression;
53-
if (newArrayExpression != null)
54-
return ExtractArguments(newArrayExpression, value);
32+
private List<StepArgument> _arguments;
5533

56-
var newExpression = expression as NewExpression;
57-
if (newExpression != null)
58-
return ExtractArguments(newExpression, value);
59-
60-
var unaryExpression = expression as UnaryExpression;
61-
if (unaryExpression != null)
62-
return ExtractArguments(unaryExpression, value);
63-
64-
var methodCallExpression = expression as MethodCallExpression;
65-
if (methodCallExpression != null)
66-
return Invoke(methodCallExpression, ExtractArguments(methodCallExpression, value));
67-
68-
return new StepArgument[0];
69-
}
70-
71-
private static IEnumerable<StepArgument> Invoke(MethodCallExpression methodCallExpression, IEnumerable<StepArgument> args)
72-
{
73-
var constantExpression = methodCallExpression.Object as ConstantExpression;
74-
var stepArguments = args.ToArray();
75-
if (constantExpression != null)
76-
return new[] { new StepArgument(() => methodCallExpression.Method.Invoke(constantExpression.Value, stepArguments.Select(s => s.Value).ToArray())) };
77-
78-
return new[] { new StepArgument(() =>
34+
public IEnumerable<StepArgument> ExtractArguments(LambdaExpression methodCallExpression, object value)
7935
{
80-
var value = stepArguments.First().Value;
81-
var parameters = stepArguments.Skip(1).Select(s => s.Value).ToArray();
82-
return methodCallExpression.Method.Invoke(value, parameters);
83-
}) };
84-
}
85-
86-
private static IEnumerable<StepArgument> ExtractArguments<T>(MethodCallExpression methodCallExpression, T value, bool extractArgsFromExpression = true)
87-
{
88-
var constants = new List<StepArgument>();
89-
foreach (var arg in methodCallExpression.Arguments)
90-
{
91-
constants.AddRange(ExtractArguments(arg, value));
92-
}
93-
94-
if (extractArgsFromExpression)
95-
{
96-
constants.AddRange(ExtractArguments(methodCallExpression.Object, value));
97-
}
98-
99-
return constants;
100-
}
101-
102-
private static IEnumerable<StepArgument> ExtractArguments<T>(UnaryExpression unaryExpression, T value)
103-
{
104-
return ExtractArguments(unaryExpression.Operand, value);
105-
}
106-
107-
private static IEnumerable<StepArgument> ExtractArguments<T>(NewExpression newExpression, T value)
108-
{
109-
var arguments = new List<StepArgument>();
110-
foreach (var argumentExpression in newExpression.Arguments)
111-
{
112-
arguments.AddRange(ExtractArguments(argumentExpression, value));
36+
_arguments = new List<StepArgument>();
37+
Visit(methodCallExpression);
38+
return _arguments;
11339
}
11440

115-
return new[] { new StepArgument(() => newExpression.Constructor.Invoke(arguments.Select(o => o.Value).ToArray())) };
116-
}
117-
118-
private static IEnumerable<StepArgument> ExtractArguments<T>(NewArrayExpression newArrayExpression, T value)
119-
{
120-
Type type = newArrayExpression.Type.GetElementType();
121-
if (type is IConvertible)
122-
return ExtractConvertibleTypeArrayConstants(newArrayExpression, type);
123-
124-
return ExtractNonConvertibleArrayConstants(newArrayExpression, type, value);
125-
}
126-
127-
private static IEnumerable<StepArgument> ExtractNonConvertibleArrayConstants<T>(NewArrayExpression newArrayExpression, Type type, T value)
128-
{
129-
var arrayElements = CreateList(type);
130-
foreach (var arrayElementExpression in newArrayExpression.Expressions)
41+
protected override Expression VisitMethodCall(MethodCallExpression node)
13142
{
132-
object arrayElement;
133-
134-
var constantExpression = arrayElementExpression as ConstantExpression;
135-
if (constantExpression != null)
136-
arrayElement = constantExpression.Value;
137-
else
138-
arrayElement = ExtractArguments(arrayElementExpression, value).Select(o => o.Value).ToArray();
139-
140-
if (arrayElement is object[])
43+
var arguments = node.Arguments.Select(a =>
14144
{
142-
foreach (var item in (object[])arrayElement)
143-
arrayElements.Add(item);
144-
}
145-
else
146-
arrayElements.Add(arrayElement);
147-
}
148-
149-
return ToArray(arrayElements).Select(o => new StepArgument(() => o));
150-
}
151-
152-
private static IEnumerable<object> ToArray(IList list)
153-
{
154-
var toArrayMethod = list.GetType().GetMethod("ToArray");
155-
yield return toArrayMethod.Invoke(list, new Type[] { });
156-
}
157-
158-
private static IList CreateList(Type type)
159-
{
160-
return (IList)typeof(List<>).MakeGenericType(type).GetConstructor(new Type[0]).Invoke(BindingFlags.CreateInstance, null, null, null);
161-
}
162-
163-
private static IEnumerable<StepArgument> ExtractConvertibleTypeArrayConstants(NewArrayExpression newArrayExpression, Type type)
164-
{
165-
var arrayElements = CreateList(type);
166-
foreach (var arrayElementExpression in newArrayExpression.Expressions)
167-
{
168-
var arrayElement = ((ConstantExpression)arrayElementExpression).Value;
169-
arrayElements.Add(Convert.ChangeType(arrayElement, arrayElementExpression.Type, null));
170-
}
171-
172-
return new[] { new StepArgument(() => ToArray(arrayElements)) };
173-
}
174-
175-
private static IEnumerable<StepArgument> ExtractArguments<T>(ConstantExpression constantExpression, T value)
176-
{
177-
var expression = constantExpression.Value as Expression;
178-
if (expression != null)
179-
{
180-
return ExtractArguments(expression, value);
45+
switch (a.NodeType)
46+
{
47+
case ExpressionType.MemberAccess:
48+
var memberExpression = (MemberExpression)a;
49+
var field = memberExpression.Member as FieldInfo;
50+
string name;
51+
Type parameterType;
52+
bool isReadOnly;
53+
if (field != null)
54+
{
55+
name = field.Name;
56+
parameterType = field.FieldType;
57+
isReadOnly = field.IsInitOnly;
58+
}
59+
else
60+
{
61+
var propertyInfo = (PropertyInfo)memberExpression.Member;
62+
name = propertyInfo.Name;
63+
parameterType = propertyInfo.PropertyType;
64+
isReadOnly = !propertyInfo.CanWrite;
65+
}
66+
67+
var getValue = GetValue(memberExpression);
68+
var setValue = isReadOnly ? null : SetValue(memberExpression, parameterType);
69+
70+
return new StepArgument(name, parameterType, getValue, setValue);
71+
default:
72+
return new StepArgument(GetValue(a));
73+
}
74+
});
75+
_arguments.AddRange(arguments);
76+
return node;
18177
}
18278

183-
var constants = new List<StepArgument>();
184-
if (constantExpression.Type == typeof(string) ||
185-
constantExpression.Type == typeof(decimal) ||
186-
constantExpression.Type.IsPrimitive ||
187-
constantExpression.Type.IsEnum ||
188-
constantExpression.Value == null)
189-
constants.Add(new StepArgument(() => constantExpression.Value));
190-
191-
return constants;
192-
}
193-
194-
private static StepArgument ExtractArgument<T>(MemberExpression memberExpression, T value)
195-
{
196-
var constExpression = memberExpression.Expression as ConstantExpression;
197-
if (constExpression != null)
198-
return ExtractConstant(memberExpression, constExpression);
199-
200-
var fieldInfo = memberExpression.Member as FieldInfo;
201-
if (fieldInfo != null)
202-
return ExtractFieldValue(memberExpression, fieldInfo, value);
203-
204-
var propertyInfo = memberExpression.Member as PropertyInfo;
205-
if (propertyInfo != null)
206-
return ExtractPropertyValue(memberExpression, propertyInfo, value);
207-
208-
throw new InvalidOperationException("Unknown expression type: " + memberExpression.GetType().Name);
209-
}
210-
211-
private static StepArgument ExtractFieldValue<T>(MemberExpression memberExpression, FieldInfo fieldInfo, T value)
212-
{
213-
if (fieldInfo.IsStatic)
214-
return new StepArgument(fieldInfo, null);
215-
216-
return new StepArgument(fieldInfo, value);
217-
}
218-
219-
private static StepArgument ExtractConstant(MemberExpression memberExpression, ConstantExpression constExpression)
220-
{
221-
var valIsConstant = constExpression != null;
222-
Type declaringType = memberExpression.Member.DeclaringType;
223-
object declaringObject = memberExpression.Member.DeclaringType;
224-
225-
if (valIsConstant)
79+
private static Func<object> GetValue(Expression a)
22680
{
227-
declaringType = constExpression.Type;
228-
declaringObject = constExpression.Value;
81+
return Expression.Lambda<Func<object>>(Expression.Convert(a, typeof(object))).Compile();
22982
}
23083

231-
var member = declaringType.GetMember(memberExpression.Member.Name, MemberTypes.Field | MemberTypes.Property, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static).Single();
232-
233-
if (member.MemberType == MemberTypes.Field)
234-
return new StepArgument((FieldInfo)member, declaringObject);
235-
236-
return new StepArgument((PropertyInfo)member, declaringObject);
237-
}
238-
239-
private static StepArgument ExtractPropertyValue<T>(MemberExpression expression, PropertyInfo member, T value)
240-
{
241-
var memberExpression = expression.Expression as MemberExpression;
242-
if (memberExpression != null)
84+
private static Action<object> SetValue(Expression a, Type parameterType)
24385
{
244-
var extractArguments = ExtractArgument(memberExpression, value).Value;
245-
return new StepArgument(member, extractArguments);
86+
var parameter = Expression.Parameter(typeof(object));
87+
var unaryExpression = Expression.Convert(parameter, parameterType);
88+
var assign = Expression.Assign(a, unaryExpression);
89+
return Expression.Lambda<Action<object>>(assign, parameter).Compile();
24690
}
247-
return new StepArgument(member, value);
24891
}
24992
}
25093
}

TestStack.BDDfy/Scanners/StepScanners/Fluent/StepArgument.cs

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Reflection;
32

43
namespace TestStack.BDDfy
54
{
@@ -8,20 +7,13 @@ public class StepArgument
87
private readonly Action<object> _set = o => { };
98
private readonly Func<object> _get;
109

11-
public StepArgument(FieldInfo member, object declaringObject)
10+
public StepArgument(string name, Type argumentType, Func<object> getValue, Action<object> setValue)
1211
{
13-
Name = member.Name;
14-
_get = () => member.GetValue(declaringObject);
15-
_set = o => member.SetValue(declaringObject, o);
16-
ArgumentType = member.FieldType;
17-
}
18-
19-
public StepArgument(PropertyInfo member, object declaringObject)
20-
{
21-
Name = member.Name;
22-
_get = () => member.GetGetMethod(true).Invoke(declaringObject, null);
23-
_set = o => member.GetSetMethod(true).Invoke(declaringObject, new[] { o });
24-
ArgumentType = member.PropertyType;
12+
Name = name;
13+
_get = getValue;
14+
if (setValue != null)
15+
_set = setValue;
16+
ArgumentType = argumentType;
2517
}
2618

2719
public StepArgument(Func<object> value)

TestStack.BDDfy/Scanners/StepScanners/StepActionFactory.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
using System;
22
using System.Reflection;
3-
using System.Security;
4-
using System.Threading;
5-
using TestStack.BDDfy.Processors;
63
using System.Threading.Tasks;
74

85
namespace TestStack.BDDfy

0 commit comments

Comments
 (0)