Skip to content

Commit

Permalink
Added support of collection initializer syntax.
Browse files Browse the repository at this point in the history
  • Loading branch information
holdenmai committed Aug 22, 2022
1 parent f0cbd31 commit 099648b
Show file tree
Hide file tree
Showing 6 changed files with 444 additions and 36 deletions.
58 changes: 49 additions & 9 deletions src/DynamicExpresso.Core/Lambda.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
Expand Down Expand Up @@ -83,23 +83,63 @@ public object Invoke(params object[] args)
var parameters = new List<Parameter>();
var declaredParameters = DeclaredParameters.ToArray();

if (args != null)
int[] actualArgOrdering = null;
object[] orderedArgs = args;
var argsAreReordered = false;
if (args != null && args.Length > 0)
{
if (declaredParameters.Length != args.Length)
throw new InvalidOperationException("Arguments count mismatch.");

for (var i = 0; i < args.Length; i++)
actualArgOrdering = new int[args.Length];
var usedParametersIndex = new Dictionary<string, int>(_parserArguments.Settings.KeyComparer);
foreach (var v in UsedParameters)
{
var parameter = new Parameter(
declaredParameters[i].Name,
declaredParameters[i].Type,
args[i]);
if (declaredParameters.Any(x => string.Equals(x.Name, v.Name, _parserArguments.Settings.KeyComparison)))
{
usedParametersIndex[v.Name] = usedParametersIndex.Count;
}
}

parameters.Add(parameter);
for (var i = 0; i < args.Length; i++)
{
if (!usedParametersIndex.TryGetValue(declaredParameters[i].Name, out var actualArgIndex))
{
actualArgIndex = -1;
}
if (actualArgIndex != i)
{
if (!argsAreReordered)
{
orderedArgs = (object[])orderedArgs.Clone();
argsAreReordered = true;
}
if (actualArgIndex == -1)
{
Array.Resize(ref orderedArgs, args.Length - 1);
}
else
{
orderedArgs[actualArgIndex] = args[i];
}
}
actualArgOrdering[i] = actualArgIndex;
}
}

return Invoke(parameters);
var result = InvokeWithUsedParameters(orderedArgs);
if (argsAreReordered)
{
for (var i = 0; i < actualArgOrdering.Length; i++)
{
var pullFrom = actualArgOrdering[i];
if (pullFrom >= 0)
{
args[i] = orderedArgs[pullFrom];
}
}
}
return result;
}

private object InvokeWithUsedParameters(object[] orderedArgs)
Expand Down
136 changes: 117 additions & 19 deletions src/DynamicExpresso.Core/Parsing/Parser.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Globalization;
Expand Down Expand Up @@ -1193,11 +1194,10 @@ private Expression ParseNew()
var constructor = applicableConstructors[0];
var newExpr = Expression.New((ConstructorInfo)constructor.MethodBase, constructor.PromotedParameters);

var memberBindings = new MemberBinding[0];
if (_token.id == TokenId.OpenCurlyBracket)
memberBindings = ParseObjectInitializer(newType);
return ParseWithObjectInitializer(newExpr, newType);

return Expression.MemberInit(newExpr, memberBindings);
return newExpr;
}

private Expression[] ParseArrayInitializerList()
Expand All @@ -1207,41 +1207,134 @@ private Expression[] ParseArrayInitializerList()
allowTrailingComma: true);
}

private MemberBinding[] ParseObjectInitializer(Type newType)
private Expression ParseWithObjectInitializer(NewExpression newExpr, Type newType)
{
ValidateToken(TokenId.OpenCurlyBracket, ErrorMessages.OpenCurlyBracketExpected);
NextToken();
var bindings = ParseMemberInitializerList(newType);
var initializedInstance = ParseMemberAndInitializerList(newExpr, newType);
ValidateToken(TokenId.CloseCurlyBracket, ErrorMessages.CloseCurlyBracketExpected);
NextToken();
return bindings;
return initializedInstance;
}

private MemberBinding[] ParseMemberInitializerList(Type newType)
private Expression ParseMemberAndInitializerList(NewExpression newExpr, Type newType)
{
var originalPos = _token.pos;
var bindingList = new List<MemberBinding>();
var actions = new List<Expression>();
var instance = Expression.Variable(newType);
var allowCollectionInit = typeof(IEnumerable).IsAssignableFrom(newType);
while (true)
{
if (_token.id == TokenId.CloseCurlyBracket) break;
ValidateToken(TokenId.Identifier, ErrorMessages.IdentifierExpected);
if (_token.id != TokenId.Identifier)
{
ParseCollectionInitalizer(newType, originalPos, bindingList, actions, instance, allowCollectionInit);
}
else
{
ParsePossibleMemberBinding(newType, originalPos, bindingList, actions, instance, allowCollectionInit);
}
if (_token.id != TokenId.Comma) break;
NextToken();
}
if (bindingList.Count == 0)
{
actions.Insert(0, Expression.Assign(instance, newExpr));
actions.Add(instance);
return Expression.Block(new ParameterExpression[] { instance }, actions);
}
return Expression.MemberInit(newExpr, bindingList.ToArray());
}

var propertyOrFieldName = _token.text;
var member = FindPropertyOrField(newType, propertyOrFieldName, false);
if (member == null)
throw CreateParseException(_token.pos, ErrorMessages.UnknownPropertyOrField, propertyOrFieldName, GetTypeName(newType));
private void ParsePossibleMemberBinding(Type newType, int originalPos, List<MemberBinding> bindingList, List<Expression> actions, ParameterExpression instance, bool allowCollectionInit)
{
ValidateToken(TokenId.Identifier, ErrorMessages.IdentifierExpected);

var propertyOrFieldName = _token.text;
var member = FindPropertyOrField(newType, propertyOrFieldName, false);
var pos = _token.pos;
if (allowCollectionInit)
{
NextToken();

ValidateToken(TokenId.Equal, ErrorMessages.EqualExpected);
//new T(){Prop = 1}
//new T(){variable = 2}
if (_token.id == TokenId.Equal && member != null)
{
if (actions.Count > 0)
{
throw CreateParseException(pos, ErrorMessages.InvalidInitializerMemberDeclarator);
}
}
else if (_token.id != TokenId.Equal || _arguments.TryGetIdentifier(propertyOrFieldName, out _) || _arguments.TryGetParameters(propertyOrFieldName, out _))
{
SetTextPos(pos);
NextToken();
ParseCollectionInitalizer(newType, pos, bindingList, actions, instance, allowCollectionInit);
return;
}
SetTextPos(pos);
NextToken();
}
if (member == null)
{
throw CreateParseException(pos, ErrorMessages.UnknownPropertyOrField, propertyOrFieldName, GetTypeName(newType));
}
NextToken();

var value = ParseExpressionSegment();
bindingList.Add(Expression.Bind(member, value));
ValidateToken(TokenId.Equal, ErrorMessages.EqualExpected);
NextToken();

if (_token.id != TokenId.Comma) break;
var value = ParseExpressionSegment();
bindingList.Add(Expression.Bind(member, value));
}

private void ParseCollectionInitalizer(Type newType, int originalPos, List<MemberBinding> bindingList, List<Expression> actions, ParameterExpression instance, bool allowCollectionInit)
{
if (!allowCollectionInit)
{
throw CreateParseException(_token.pos, ErrorMessages.CollectionInitializationNotSupported, newType, typeof(IEnumerable));
}
if (bindingList.Count > 0)
{
throw CreateParseException(originalPos, ErrorMessages.InvalidInitializerMemberDeclarator);
}
if (_token.id == TokenId.OpenCurlyBracket)
{
var pos = _token.pos;
NextToken();

if (_token.id == TokenId.Identifier)
{
var identifierName = _token.text;
NextToken();
if (_token.id == TokenId.Equal && !_arguments.TryGetIdentifier(identifierName, out _) && !_arguments.TryGetParameters(identifierName, out _))
{
throw CreateParseException(_token.pos, ErrorMessages.InvalidInitializerMemberDeclarator);
}
else
{
SetTextPos(pos);
NextToken();
}
}
else
{
SetTextPos(pos);
ParseExpressionSegment();
}
actions.Add(ParseMethodInvocation(newType, instance, _token.pos, "Add", TokenId.OpenCurlyBracket, ErrorMessages.OpenCurlyBracketExpected, TokenId.CloseCurlyBracket, ErrorMessages.CloseCurlyBracketExpected));
}
else
{
var args = new[] { ParseExpressionSegment() };
var addMethod = ParseNormalMethodInvocation(newType, instance, _token.pos, "Add", args);
if (addMethod == null)
{
throw CreateParseException(_token.pos, ErrorMessages.UnableToFindAppropriateAddMethod, GetTypeName(newType));
}
actions.Add(addMethod);
}
return bindingList.ToArray();
}

private Expression ParseLambdaInvocation(LambdaExpression lambda, int errorPos)
Expand Down Expand Up @@ -1573,7 +1666,12 @@ private Expression GeneratePropertyOrFieldExpression(Type type, Expression insta

private Expression ParseMethodInvocation(Type type, Expression instance, int errorPos, string methodName)
{
var args = ParseArgumentList();
return ParseMethodInvocation(type, instance, errorPos, methodName, TokenId.OpenParen, ErrorMessages.OpenParenExpected, TokenId.CloseParen, ErrorMessages.CloseParenOrCommaExpected);

}
private Expression ParseMethodInvocation(Type type, Expression instance, int errorPos, string methodName, TokenId open, string openExpected, TokenId close, string closeExpected)
{
var args = ParseArgumentList(open, openExpected, close, closeExpected);

var methodInvocationExpression = ParseNormalMethodInvocation(type, instance, errorPos, methodName, args);
if (methodInvocationExpression == null && instance != null)
Expand Down
38 changes: 33 additions & 5 deletions src/DynamicExpresso.Core/Resources/ErrorMessages.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions src/DynamicExpresso.Core/Resources/ErrorMessages.resx
Original file line number Diff line number Diff line change
Expand Up @@ -267,4 +267,13 @@
<data name="UnsupportedMultidimensionalArrays" xml:space="preserve">
<value>Multidimensional arrays are not supported</value>
</data>
<data name="InvalidInitializerMemberDeclarator" xml:space="preserve">
<value>Invalid initializer member declarator</value>
</data>
<data name="CollectionInitializationNotSupported" xml:space="preserve">
<value>Cannot initialize type '{0}' with a collection initializer because it does not implement '{1}'</value>
</data>
<data name="UnableToFindAppropriateAddMethod" xml:space="preserve">
<value>The best overloaded Add method '{0}.Add' for the collection initializer has some invalid arguments</value>
</data>
</root>
Loading

0 comments on commit 099648b

Please sign in to comment.