Skip to content

Commit 058d6d6

Browse files
authored
Merge pull request #103 from btnlq/master
Improved indexing, close #39
2 parents 0b8c0c4 + c33eb06 commit 058d6d6

File tree

6 files changed

+233
-35
lines changed

6 files changed

+233
-35
lines changed

src/DynamicExpresso.Core/Parsing/Parser.cs

Lines changed: 95 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -83,22 +83,30 @@ private Expression ParseExpressionSegment()
8383
// MSDN C# "Operator precedence and associativity"
8484
// https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/
8585

86-
return ParseAssignement();
86+
return ParseAssignment();
8787
}
8888

8989
// = operator
90-
private Expression ParseAssignement()
90+
private Expression ParseAssignment()
9191
{
9292
var left = ParseConditional();
9393
if (_token.id == TokenId.Equal)
9494
{
9595
if (!_arguments.Settings.AssignmentOperators.HasFlag(AssignmentOperators.AssignmentEqual))
9696
throw new AssignmentOperatorDisabledException("=", _token.pos);
9797

98+
if (!IsWritable(left))
99+
throw CreateParseException(_token.pos, ErrorMessages.ExpressionMustBeWritable);
100+
98101
NextToken();
99-
var right = ParseAssignement();
100-
CheckAndPromoteOperands(typeof(ParseSignatures.IEqualitySignatures), ref left, ref right);
101-
left = Expression.Assign(left, right);
102+
103+
var right = ParseAssignment();
104+
var promoted = PromoteExpression(right, left.Type, true);
105+
if (promoted == null)
106+
throw CreateParseException(_token.pos, ErrorMessages.CannotConvertValue,
107+
GetTypeName(right.Type), GetTypeName(left.Type));
108+
109+
left = Expression.Assign(left, promoted);
102110
}
103111
return left;
104112
}
@@ -1088,12 +1096,17 @@ private Expression ParseElementAccess(Expression expr)
10881096
NextToken();
10891097
if (expr.Type.IsArray)
10901098
{
1091-
if (expr.Type.GetArrayRank() != 1 || args.Length != 1)
1092-
throw CreateParseException(errorPos, ErrorMessages.CannotIndexMultiDimArray);
1093-
var index = PromoteExpression(args[0], typeof(int), true);
1094-
if (index == null)
1095-
throw CreateParseException(errorPos, ErrorMessages.InvalidIndex);
1096-
return Expression.ArrayIndex(expr, index);
1099+
if (expr.Type.GetArrayRank() != args.Length)
1100+
throw CreateParseException(errorPos, ErrorMessages.IncorrectNumberOfIndexes);
1101+
1102+
for (int i = 0; i < args.Length; i++)
1103+
{
1104+
args[i] = PromoteExpression(args[i], typeof(int), true);
1105+
if (args[i] == null)
1106+
throw CreateParseException(errorPos, ErrorMessages.InvalidIndex);
1107+
}
1108+
1109+
return Expression.ArrayAccess(expr, args);
10971110
}
10981111

10991112
var applicableMethods = FindIndexer(expr.Type, args);
@@ -1109,9 +1122,8 @@ private Expression ParseElementAccess(Expression expr)
11091122
GetTypeName(expr.Type));
11101123
}
11111124

1112-
var method = applicableMethods[0];
1113-
1114-
return Expression.Call(expr, (MethodInfo)method.MethodBase, method.PromotedParameters);
1125+
var indexer = (IndexerData) applicableMethods[0];
1126+
return Expression.Property(expr, indexer.Indexer, indexer.PromotedParameters);
11151127
}
11161128

11171129
private static bool IsNullableType(Type type)
@@ -1266,10 +1278,9 @@ private MethodData[] FindIndexer(Type type, Expression[] args)
12661278
MemberInfo[] members = t.GetDefaultMembers();
12671279
if (members.Length != 0)
12681280
{
1269-
IEnumerable<MethodBase> methods = members.
1281+
IEnumerable<MethodData> methods = members.
12701282
OfType<PropertyInfo>().
1271-
Select(p => (MethodBase)p.GetGetMethod()).
1272-
Where(m => m != null);
1283+
Select(p => (MethodData) new IndexerData(p));
12731284

12741285
var applicableMethods = FindBestMethod(methods, args);
12751286
if (applicableMethods.Length > 0)
@@ -1316,9 +1327,13 @@ private static void AddInterface(List<Type> types, Type type)
13161327
}
13171328

13181329
private static MethodData[] FindBestMethod(IEnumerable<MethodBase> methods, Expression[] args)
1330+
{
1331+
return FindBestMethod(methods.Select(MethodData.Gen), args);
1332+
}
1333+
1334+
private static MethodData[] FindBestMethod(IEnumerable<MethodData> methods, Expression[] args)
13191335
{
13201336
var applicable = methods.
1321-
Select(m => new MethodData { MethodBase = m, Parameters = m.GetParameters() }).
13221337
Where(m => CheckIfMethodIsApplicableAndPrepareIt(m, args)).
13231338
ToArray();
13241339
if (applicable.Length > 1)
@@ -1417,7 +1432,7 @@ private static bool CheckIfMethodIsApplicableAndPrepareIt(MethodData method, Exp
14171432

14181433
method.PromotedParameters = promotedArgs.ToArray();
14191434

1420-
if (method.MethodBase.IsGenericMethodDefinition &&
1435+
if (method.MethodBase != null && method.MethodBase.IsGenericMethodDefinition &&
14211436
method.MethodBase is MethodInfo)
14221437
{
14231438
var methodInfo = (MethodInfo)method.MethodBase;
@@ -1626,6 +1641,30 @@ private static bool IsCompatibleWith(Type source, Type target)
16261641
return false;
16271642
}
16281643

1644+
private static bool IsWritable(Expression expression)
1645+
{
1646+
switch (expression.NodeType)
1647+
{
1648+
case ExpressionType.Index:
1649+
PropertyInfo indexer = ((IndexExpression)expression).Indexer;
1650+
return indexer == null || indexer.CanWrite;
1651+
case ExpressionType.MemberAccess:
1652+
MemberInfo member = ((MemberExpression)expression).Member;
1653+
var prop = member as PropertyInfo;
1654+
if (prop != null)
1655+
return prop.CanWrite;
1656+
else
1657+
{
1658+
var field = (FieldInfo)member;
1659+
return !(field.IsInitOnly || field.IsLiteral);
1660+
}
1661+
case ExpressionType.Parameter:
1662+
return true;
1663+
}
1664+
1665+
return false;
1666+
}
1667+
16291668
// from http://stackoverflow.com/a/1075059/209727
16301669
private static Type FindAssignableGenericType(Type givenType, Type genericTypeDefinition)
16311670
{
@@ -2156,6 +2195,43 @@ private class MethodData
21562195
public ParameterInfo[] Parameters;
21572196
public Expression[] PromotedParameters;
21582197
public bool HasParamsArray;
2198+
2199+
public static MethodData Gen(MethodBase method)
2200+
{
2201+
return new MethodData
2202+
{
2203+
MethodBase = method,
2204+
Parameters = method.GetParameters()
2205+
};
2206+
}
2207+
}
2208+
2209+
private class IndexerData : MethodData
2210+
{
2211+
public readonly PropertyInfo Indexer;
2212+
2213+
public IndexerData(PropertyInfo indexer)
2214+
{
2215+
Indexer = indexer;
2216+
2217+
var method = indexer.GetGetMethod();
2218+
if (method != null)
2219+
{
2220+
Parameters = method.GetParameters();
2221+
}
2222+
else
2223+
{
2224+
method = indexer.GetSetMethod();
2225+
Parameters = RemoveLast(method.GetParameters());
2226+
}
2227+
}
2228+
2229+
private static T[] RemoveLast<T>(T[] array)
2230+
{
2231+
T[] result = new T[array.Length - 1];
2232+
Array.Copy(array, 0, result, 0, result.Length);
2233+
return result;
2234+
}
21592235
}
21602236
}
21612237
}

src/DynamicExpresso.Core/Resources/ErrorMessages.Designer.cs

Lines changed: 18 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/DynamicExpresso.Core/Resources/ErrorMessages.resx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,6 @@
135135
<data name="CannotConvertValue" xml:space="preserve">
136136
<value>A value of type '{0}' cannot be converted to type '{1}'</value>
137137
</data>
138-
<data name="CannotIndexMultiDimArray" xml:space="preserve">
139-
<value>Indexing of multi-dimensional arrays is not supported</value>
140-
</data>
141138
<data name="CloseBracketOrCommaExpected" xml:space="preserve">
142139
<value>']' or ',' expected</value>
143140
</data>
@@ -159,6 +156,9 @@
159156
<data name="ExpressionExpected" xml:space="preserve">
160157
<value>Expression expected</value>
161158
</data>
159+
<data name="ExpressionMustBeWritable" xml:space="preserve">
160+
<value>Expression must be writable</value>
161+
</data>
162162
<data name="FirstExprMustBeBool" xml:space="preserve">
163163
<value>The first expression must be of type 'Boolean'</value>
164164
</data>
@@ -174,6 +174,9 @@
174174
<data name="IncompatibleOperands" xml:space="preserve">
175175
<value>Operator '{0}' incompatible with operand types '{1}' and '{2}'</value>
176176
</data>
177+
<data name="IncorrectNumberOfIndexes" xml:space="preserve">
178+
<value>Incorrect number of indexes</value>
179+
</data>
177180
<data name="InvalidCharacter" xml:space="preserve">
178181
<value>Syntax error '{0}'</value>
179182
</data>

test/DynamicExpresso.UnitTest/InvalidExpressionTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public void Invalid_equal_assignment_operator_left_is_literal()
2828
{
2929
var target = new Interpreter();
3030

31-
Assert.Throws<ArgumentException>(() => target.Eval("352=234"));
31+
Assert.Throws<ParseException>(() => target.Eval("352=234"));
3232
}
3333

3434
[Test]

0 commit comments

Comments
 (0)