Skip to content

Commit 23d9a7b

Browse files
committed
Multidimensional indexing looks OK
1 parent b91a0fc commit 23d9a7b

File tree

3 files changed

+140
-98
lines changed

3 files changed

+140
-98
lines changed

CodingSeb.ExpressionEvaluator.Tests/ExpressionEvaluatorTests.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1330,7 +1330,7 @@ public object OnTheFlyIndexingEvaluation(string expression)
13301330

13311331
private void Evaluator_PreEvaluateIndexing(object sender, IndexingPreEvaluationEventArg e)
13321332
{
1333-
if(e.This is int intValue && e.EvaluateArg() is string text)
1333+
if(e.This is int intValue && e.EvaluateArg(0) is string text)
13341334
{
13351335
e.Value = string.Join(",", Enumerable.Repeat(text, intValue));
13361336
}
@@ -2701,6 +2701,16 @@ ExpressionEvaluator evaluatorForMethodArgs()
27012701

27022702
#endregion
27032703

2704+
#region For multidimensional indexing
2705+
2706+
yield return new TestCaseData(new ExpressionEvaluator(new Dictionary<string, object> { { "doubleIndexObject", new ClassForIndexing() } })
2707+
, "doubleIndexObject[2,3]"
2708+
, null)
2709+
.Returns(5)
2710+
.SetCategory("Multidimensional indexing");
2711+
2712+
#endregion
2713+
27042714
#endregion
27052715
}
27062716
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
namespace CodingSeb.ExpressionEvaluator.Tests
2+
{
3+
public class ClassForIndexing
4+
{
5+
public int this[int i1, int i2]
6+
{
7+
get
8+
{
9+
return i1 + i2;
10+
}
11+
set
12+
{}
13+
}
14+
15+
public string this[string test]
16+
{
17+
get
18+
{
19+
return test + test;
20+
}
21+
set
22+
{}
23+
}
24+
}
25+
}

CodingSeb.ExpressionEvaluator/ExpressionEvaluator.cs

Lines changed: 104 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public partial class ExpressionEvaluator
3838
protected static readonly Regex otherBasesNumberRegex = new Regex("^(?<sign>[+-])?(?<value>0(?<type>x)([0-9a-f][0-9a-f_]*[0-9a-f]|[0-9a-f])|0(?<type>b)([01][01_]*[01]|[01]))", RegexOptions.IgnoreCase | RegexOptions.Compiled);
3939
protected static readonly Regex stringBeginningRegex = new Regex("^(?<interpolated>[$])?(?<escaped>[@])?[\"]", RegexOptions.Compiled);
4040
protected static readonly Regex internalCharRegex = new Regex(@"^['](\\[\\'0abfnrtv]|[^'])[']", RegexOptions.Compiled);
41-
protected static readonly Regex indexingBeginningRegex = new Regex(@"^[?]?\[", RegexOptions.Compiled);
41+
protected static readonly Regex indexingBeginningRegex = new Regex(@"^(?<nullConditional>[?])?\[", RegexOptions.Compiled);
4242
protected static readonly Regex arrayTypeDetectionRegex = new Regex(@"^(\s*(\[(?>(?>\s+)|[,])*)\])+", RegexOptions.Compiled);
4343
protected static readonly Regex assignationOrPostFixOperatorRegex = new Regex(@"^(?>\s*)((?<assignmentPrefix>[+\-*/%&|^]|<<|>>|\?\?)?=(?![=>])|(?<postfixOperator>([+][+]|--)(?![\p{L}_0-9])))");
4444
protected static readonly Regex genericsDecodeRegex = new Regex("(?<name>[^,<>]+)(?<isgeneric>[<](?>[^<>]+|(?<gentag>[<])|(?<-gentag>[>]))*(?(gentag)(?!))[>])?", RegexOptions.Compiled);
@@ -229,45 +229,9 @@ protected enum TryBlockEvaluatedState
229229
protected virtual IList<ExpressionOperator> RightOperandOnlyOperatorsEvaluationDictionary => rightOperandOnlyOperatorsEvaluationDictionary;
230230
protected virtual IList<IDictionary<ExpressionOperator, Func<dynamic, dynamic, object>>> OperatorsEvaluations => operatorsEvaluations;
231231

232-
protected static object IndexingOperatorFunc(dynamic left, dynamic right)
233-
{
234-
if (left is NullConditionalNullValue)
235-
{
236-
return left;
237-
}
238-
else if (left is BubbleExceptionContainer)
239-
{
240-
return left;
241-
}
242-
Type type = ((object)left).GetType();
243-
244-
if (left is IDictionary<string, object> dictionaryLeft)
245-
{
246-
return dictionaryLeft[right];
247-
}
248-
else if (type.GetMethod("Item", new Type[] { ((object)right).GetType() }) is MethodInfo methodInfo)
249-
{
250-
return methodInfo.Invoke(left, new object[] { right });
251-
}
252-
253-
return left[right];
254-
}
255-
256232
protected static readonly IList<IDictionary<ExpressionOperator, Func<dynamic, dynamic, object>>> operatorsEvaluations =
257233
new List<IDictionary<ExpressionOperator, Func<dynamic, dynamic, object>>>()
258234
{
259-
new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>()
260-
{
261-
{ExpressionOperator.Indexing, IndexingOperatorFunc},
262-
{ExpressionOperator.IndexingWithNullConditional, (dynamic left, dynamic right) =>
263-
{
264-
if(left == null)
265-
return new NullConditionalNullValue();
266-
267-
return IndexingOperatorFunc(left, right);
268-
}
269-
},
270-
},
271235
new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>()
272236
{
273237
{ExpressionOperator.UnaryPlus, (dynamic _, dynamic right) => +right },
@@ -2779,54 +2743,24 @@ protected virtual bool EvaluateIndexing(string expression, Stack<object> stack,
27792743

27802744
if (indexingBeginningMatch.Success)
27812745
{
2782-
StringBuilder innerExp = new StringBuilder();
27832746
i += indexingBeginningMatch.Length;
2784-
int bracketCount = 1;
2785-
for (; i < expression.Length; i++)
2786-
{
2787-
Match internalStringMatch = stringBeginningRegex.Match(expression.Substring(i));
2788-
2789-
if (internalStringMatch.Success)
2790-
{
2791-
string innerString = internalStringMatch.Value + GetCodeUntilEndOfString(expression.Substring(i + internalStringMatch.Length), internalStringMatch);
2792-
innerExp.Append(innerString);
2793-
i += innerString.Length - 1;
2794-
}
2795-
else
2796-
{
2797-
string s = expression.Substring(i, 1);
2798-
2799-
if (s.Equals("[")) bracketCount++;
2800-
2801-
if (s.Equals("]"))
2802-
{
2803-
bracketCount--;
2804-
if (bracketCount == 0) break;
2805-
}
2806-
innerExp.Append(s);
2807-
}
2808-
}
2809-
2810-
if (bracketCount > 0)
2811-
{
2812-
string beVerb = bracketCount == 1 ? "is" : "are";
2813-
throw new Exception($"{bracketCount} ']' character {beVerb} missing in expression : [{expression}]");
2814-
}
28152747

28162748
dynamic left = stack.Pop();
28172749

2818-
if (left is NullConditionalNullValue)
2750+
List<string> indexingArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expression, ref i, true, startChar: "[", endChar: "]");
2751+
2752+
if (left is NullConditionalNullValue || left is BubbleExceptionContainer)
28192753
{
28202754
stack.Push(left);
28212755
return true;
28222756
}
2823-
else if (left is BubbleExceptionContainer)
2757+
else if(indexingBeginningMatch.Groups["nullConditional"].Success && left == null)
28242758
{
2825-
stack.Push(left);
2759+
stack.Push(new NullConditionalNullValue());
28262760
return true;
28272761
}
28282762

2829-
IndexingPreEvaluationEventArg indexingPreEvaluationEventArg = new IndexingPreEvaluationEventArg(innerExp.ToString(), this, left);
2763+
IndexingPreEvaluationEventArg indexingPreEvaluationEventArg = new IndexingPreEvaluationEventArg(indexingArgs, this, left);
28302764

28312765
PreEvaluateIndexing?.Invoke(this, indexingPreEvaluationEventArg);
28322766

@@ -2840,15 +2774,67 @@ protected virtual bool EvaluateIndexing(string expression, Stack<object> stack,
28402774
}
28412775
else
28422776
{
2843-
dynamic right = Evaluate(innerExp.ToString());
2844-
ExpressionOperator op = indexingBeginningMatch.Length == 2 ? ExpressionOperator.IndexingWithNullConditional : ExpressionOperator.Indexing;
2777+
Match assignationOrPostFixOperatorMatch = null;
2778+
dynamic valueToPush = null;
2779+
List<dynamic> oIndexingArgs = indexingArgs.ConvertAll(Evaluate);
2780+
PropertyInfo[] itemProperties = null;
28452781

2846-
if (OptionForceIntegerNumbersEvaluationsAsDoubleByDefault && right is double && Regex.IsMatch(innerExp.ToString(), @"^\d+$"))
2847-
right = (int)right;
2782+
if (!(left is IDictionary<string, object>))
2783+
{
2784+
Type type = ((object)left).GetType();
28482785

2849-
Match assignationOrPostFixOperatorMatch = null;
2786+
if(type.IsArray && OptionForceIntegerNumbersEvaluationsAsDoubleByDefault)
2787+
{
2788+
oIndexingArgs = oIndexingArgs.Select(o => o is double ? (int)o : o).ToList();
2789+
}
2790+
else
2791+
{
2792+
itemProperties = type.GetProperties()
2793+
.Where(property => property.GetIndexParameters().Length > 0
2794+
&& property.GetIndexParameters().Length == oIndexingArgs.Count
2795+
&& property.GetIndexParameters().All(parameter => parameter.ParameterType.IsAssignableFrom(oIndexingArgs[parameter.Position].GetType())))
2796+
.ToArray();
28502797

2851-
dynamic valueToPush = null;
2798+
if (itemProperties.Length == 0 && OptionForceIntegerNumbersEvaluationsAsDoubleByDefault)
2799+
{
2800+
List<dynamic> backupIndexedArgs = oIndexingArgs.ToList();
2801+
2802+
itemProperties = type.GetProperties()
2803+
.Where(property => property.Name.Equals("Item")
2804+
&& property.GetIndexParameters().Length == oIndexingArgs.Count
2805+
&& property.GetIndexParameters().All(parameter =>
2806+
{
2807+
if (parameter.ParameterType.IsAssignableFrom(((object)oIndexingArgs[parameter.Position]).GetType()))
2808+
{
2809+
return true;
2810+
}
2811+
else if (parameter.ParameterType == typeof(int) && oIndexingArgs[parameter.Position] is double)
2812+
{
2813+
oIndexingArgs[parameter.Position] = (int)oIndexingArgs[parameter.Position];
2814+
return true;
2815+
}
2816+
else
2817+
{
2818+
return false;
2819+
}
2820+
}))
2821+
.ToArray();
2822+
2823+
if (itemProperties.Length == 0)
2824+
oIndexingArgs = backupIndexedArgs;
2825+
}
2826+
}
2827+
}
2828+
2829+
object GetIndexedObject()
2830+
{
2831+
if (left is IDictionary<string, object> dictionaryLeft)
2832+
return dictionaryLeft[oIndexingArgs[0]];
2833+
else if (itemProperties?.Length > 0)
2834+
return itemProperties[0].GetValue(left, oIndexingArgs.Cast<object>().ToArray());
2835+
else
2836+
return left[oIndexingArgs[0]];
2837+
}
28522838

28532839
if (OptionIndexingAssignationActive && (assignationOrPostFixOperatorMatch = assignationOrPostFixOperatorRegex.Match(expression.Substring(i + 1))).Success)
28542840
{
@@ -2860,31 +2846,42 @@ protected virtual bool EvaluateIndexing(string expression, Stack<object> stack,
28602846
if (stack.Count > 1)
28612847
throw new ExpressionEvaluatorSyntaxErrorException($"The left part of {exceptionContext} must be a variable, a property or an indexer.");
28622848

2863-
if (op == ExpressionOperator.IndexingWithNullConditional)
2849+
if (indexingBeginningMatch.Groups["nullConditional"].Success)
28642850
throw new ExpressionEvaluatorSyntaxErrorException($"Null conditional is not usable left to {exceptionContext}");
28652851

28662852
if (postFixOperator)
28672853
{
28682854
if (left is IDictionary<string, object> dictionaryLeft)
2869-
valueToPush = assignationOrPostFixOperatorMatch.Groups["postfixOperator"].Value.Equals("++") ? dictionaryLeft[right]++ : dictionaryLeft[right]--;
2855+
{
2856+
valueToPush = assignationOrPostFixOperatorMatch.Groups["postfixOperator"].Value.Equals("++") ? dictionaryLeft[oIndexingArgs[0]]++ : dictionaryLeft[oIndexingArgs[0]]--;
2857+
}
2858+
else if (itemProperties?.Length > 0)
2859+
{
2860+
valueToPush = itemProperties[0].GetValue(left, oIndexingArgs.Cast<object>().ToArray());
2861+
itemProperties[0].SetValue(left, assignationOrPostFixOperatorMatch.Groups["postfixOperator"].Value.Equals("++") ? valueToPush + 1 : valueToPush - 1, oIndexingArgs.Cast<object>().ToArray());
2862+
}
28702863
else
2871-
valueToPush = assignationOrPostFixOperatorMatch.Groups["postfixOperator"].Value.Equals("++") ? left[right]++ : left[right]--;
2864+
{
2865+
valueToPush = assignationOrPostFixOperatorMatch.Groups["postfixOperator"].Value.Equals("++") ? left[oIndexingArgs[0]]++ : left[oIndexingArgs[0]]--;
2866+
}
28722867
}
28732868
else
28742869
{
2875-
valueToPush = ManageKindOfAssignation(expression, ref i, assignationOrPostFixOperatorMatch, () => OperatorsEvaluations[0][op](left, right));
2870+
valueToPush = ManageKindOfAssignation(expression, ref i, assignationOrPostFixOperatorMatch, GetIndexedObject);
28762871

28772872
if (left is IDictionary<string, object> dictionaryLeft)
2878-
dictionaryLeft[right] = valueToPush;
2873+
dictionaryLeft[oIndexingArgs[0]] = valueToPush;
2874+
else if (itemProperties?.Length > 0)
2875+
itemProperties[0].SetValue(left, valueToPush, oIndexingArgs.Cast<object>().ToArray());
28792876
else
2880-
left[right] = valueToPush;
2877+
left[oIndexingArgs[0]] = valueToPush;
28812878

28822879
stack.Clear();
28832880
}
28842881
}
28852882
else
28862883
{
2887-
valueToPush = OperatorsEvaluations[0][op](left, right);
2884+
valueToPush = GetIndexedObject();
28882885
}
28892886

28902887
stack.Push(valueToPush);
@@ -4391,8 +4388,6 @@ protected ExpressionOperator()
43914388
public static readonly ExpressionOperator ShiftBitsRight = new ExpressionOperator();
43924389
public static readonly ExpressionOperator NullCoalescing = new ExpressionOperator();
43934390
public static readonly ExpressionOperator Cast = new ExpressionOperator();
4394-
public static readonly ExpressionOperator Indexing = new ExpressionOperator();
4395-
public static readonly ExpressionOperator IndexingWithNullConditional = new ExpressionOperator();
43964391

43974392
public override bool Equals(object obj)
43984393
{
@@ -4820,17 +4815,17 @@ public FunctionPreEvaluationEventArg(string name, List<string> args = null, Expr
48204815
/// </summary>
48214816
public partial class IndexingPreEvaluationEventArg : EventArgs
48224817
{
4823-
public IndexingPreEvaluationEventArg(string arg, ExpressionEvaluator evaluator, object onInstance)
4818+
public IndexingPreEvaluationEventArg(List<string> args, ExpressionEvaluator evaluator, object onInstance)
48244819
{
4825-
Arg = arg;
4820+
Args = args;
48264821
This = onInstance;
48274822
Evaluator = evaluator;
48284823
}
48294824

48304825
/// <summary>
48314826
/// The not evaluated args of the indexing
48324827
/// </summary>
4833-
public string Arg { get; set; }
4828+
public List<string> Args { get; } = new List<string>();
48344829

48354830
/// <summary>
48364831
/// The instance of the object on which the indexing is called.
@@ -4866,18 +4861,30 @@ public object Value
48664861
/// Get the values of the indexing's args.
48674862
/// </summary>
48684863
/// <returns></returns>
4869-
public object EvaluateArg()
4864+
public object[] EvaluateArgs()
48704865
{
4871-
return Evaluator.Evaluate(Arg);
4866+
return Args.ConvertAll(arg => Evaluator.Evaluate(arg)).ToArray();
48724867
}
48734868

48744869
/// <summary>
4875-
/// Get the values of the indexing's args.
4870+
/// Get the value of the indexing's arg at the specified index
48764871
/// </summary>
4877-
/// <returns></returns>
4878-
public T EvaluateArg<T>()
4872+
/// <param name="index">The index of the indexing's arg to evaluate</param>
4873+
/// <returns>The evaluated arg</returns>
4874+
public object EvaluateArg(int index)
48794875
{
4880-
return Evaluator.Evaluate<T>(Arg);
4876+
return Evaluator.Evaluate(Args[index]);
4877+
}
4878+
4879+
/// <summary>
4880+
/// Get the value of the indexing's arg at the specified index
4881+
/// </summary>
4882+
/// <typeparam name="T">The type of the result to get. (Do a cast)</typeparam>
4883+
/// <param name="index">The index of the indexing's arg to evaluate</param>
4884+
/// <returns>The evaluated arg casted in the specified type</returns>
4885+
public T EvaluateArg<T>(int index)
4886+
{
4887+
return Evaluator.Evaluate<T>(Args[index]);
48814888
}
48824889

48834890
/// <summary>

0 commit comments

Comments
 (0)