/
SliceExpression.cs
145 lines (129 loc) · 6.06 KB
/
SliceExpression.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
using Debug = System.Diagnostics.Debug;
using RuntimeHelpers = System.Runtime.CompilerServices.RuntimeHelpers;
namespace DotNext.Linq.Expressions;
using static Reflection.TypeExtensions;
/// <summary>
/// Represents slice of collection using range.
/// </summary>
public sealed class SliceExpression : CustomExpression
{
private readonly MethodInfo? slice; // if null then array
private readonly PropertyInfo? count; // if null then object supports Slice method with Range parameter
/// <summary>
/// Initializes a new slice of collection or array.
/// </summary>
/// <param name="collection">The collection or array.</param>
/// <param name="range">The requested range of collection or array. Should of type <see cref="Range"/>.</param>
/// <exception cref="ArgumentException"><paramref name="collection"/> doesn't implement <c>Slice</c> method, <c>Length</c> or <c>Count</c> property; or <paramref name="range"/> is not of type <see cref="Range"/>.</exception>
public SliceExpression(Expression collection, Expression range)
{
ArgumentNullException.ThrowIfNull(collection);
ArgumentNullException.ThrowIfNull(range);
if (range.Type != typeof(Range))
throw new ArgumentException(ExceptionMessages.TypeExpected<Range>(), nameof(range));
var resolved = false;
if (collection.Type.IsSZArray)
{
slice = null;
count = null;
resolved = true;
}
else if (collection.Type == typeof(string))
{
slice = new Func<string, Range, string>(StringExtensions.Substring).Method;
count = null;
resolved = true;
}
else
{
foreach (var slice in GetSliceMethods(collection.Type))
{
var parameters = slice.GetParameters();
if (parameters.LongLength == 1L && parameters[0].ParameterType == typeof(Range))
{
count = null;
this.slice = slice;
resolved = true;
break;
}
var intType = typeof(int);
if (parameters.LongLength == 2L && parameters[0].ParameterType == intType && parameters[1].ParameterType == intType)
{
count = CollectionAccessExpression.GetCountProperty(collection.Type) ?? throw new ArgumentException(ExceptionMessages.CollectionExpected(collection.Type), nameof(collection));
this.slice = slice;
resolved = true;
break;
}
}
}
Range = resolved ? range : throw new ArgumentException(ExceptionMessages.CollectionExpected(collection.Type), nameof(collection));
Collection = collection;
}
private static IEnumerable<MethodInfo> GetSliceMethods(Type collection)
{
foreach (var lookup in collection.GetBaseTypes(includeTopLevel: true, includeInterfaces: collection.IsInterface))
{
foreach (var member in lookup.FindMembers(MemberTypes.Method, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, Type.FilterName, "Slice"))
{
if (member is MethodInfo method)
yield return method;
}
}
}
/// <summary>
/// Gets result type of asynchronous operation.
/// </summary>
public override Type Type => slice?.ReturnType ?? Collection.Type;
/// <summary>
/// Gets collection.
/// </summary>
public Expression Collection { get; }
/// <summary>
/// Gets slice range.
/// </summary>
public Expression Range { get; }
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, typeof(RuntimeHelpers))]
private static MethodCallExpression SubArray(Expression array, Expression range)
{
MethodInfo? subArray = typeof(RuntimeHelpers).GetMethod(nameof(RuntimeHelpers.GetSubArray), 1, [Type.MakeGenericMethodParameter(0).MakeArrayType(), typeof(Range)]);
Debug.Assert(subArray is not null);
subArray = subArray.MakeGenericMethod(array.Type.GetElementType()!);
return Call(subArray, array, range.Reduce());
}
private static BlockExpression SubCollection(Expression collection, MethodInfo slice, PropertyInfo count, Expression range)
{
var offsetAndLengthCall = RangeExpression.GetOffsetAndLength(range, Property(collection, count), out var offsetAndLength, out var offsetField, out var lengthField);
return Block([offsetAndLength], Assign(offsetAndLength, offsetAndLengthCall), Call(collection, slice, offsetField, lengthField));
}
/// <summary>
/// Translates this expression into predefined set of expressions
/// using Lowering technique.
/// </summary>
/// <returns>Translated expression.</returns>
public override Expression Reduce()
{
ParameterExpression? temp = Collection is ParameterExpression ? null : Variable(Collection.Type);
Expression result;
if (slice is null)
result = SubArray(temp ?? Collection, Range);
else if (count is null)
result = slice.IsStatic ? Call(slice, temp ?? Collection, Range.Reduce()) : Call(temp ?? Collection, slice, Range.Reduce());
else
result = SubCollection(temp ?? Collection, slice, count, Range);
return temp is null ? result : Block(Type, [temp], Assign(temp, Collection), result);
}
/// <summary>
/// Visit children expressions.
/// </summary>
/// <param name="visitor">Expression visitor.</param>
/// <returns>Potentially modified expression if one of children expressions is modified during visit.</returns>
protected override SliceExpression VisitChildren(ExpressionVisitor visitor)
{
var range = visitor.Visit(Range);
var collection = visitor.Visit(Collection);
return ReferenceEquals(range, Range) && ReferenceEquals(collection, Collection) ? this : new(collection, range);
}
}