using System;
|
using System.Collections;
|
using System.Collections.Generic;
|
using System.Globalization;
|
using System.Linq;
|
using System.Linq.Expressions;
|
using System.Reflection;
|
using System.Text;
|
using DynamicExpresso.Exceptions;
|
using DynamicExpresso.Reflection;
|
using DynamicExpresso.Resolution;
|
using DynamicExpresso.Resources;
|
using Microsoft.CSharp.RuntimeBinder;
|
|
// Code based on the Dynamic.cs file of the DynamicQuery sample by Microsoft
|
// http://msdn.microsoft.com/en-us/vstudio/bb894665.aspx
|
// http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
|
//
|
// Copyright (C) Microsoft Corporation. All rights reserved.
|
|
namespace DynamicExpresso.Parsing
|
{
|
internal class Parser
|
{
|
public static Expression Parse(ParserArguments arguments)
|
{
|
return new Parser(arguments).Parse();
|
}
|
|
private const NumberStyles ParseLiteralNumberStyle = NumberStyles.AllowLeadingSign;
|
private const NumberStyles ParseLiteralUnsignedNumberStyle = NumberStyles.AllowLeadingSign;
|
private const NumberStyles ParseLiteralHexNumberStyle = NumberStyles.HexNumber;
|
private const NumberStyles ParseLiteralDecimalNumberStyle = NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint;
|
private const NumberStyles ParseLiteralDoubleNumberStyle = NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent;
|
private static readonly CultureInfo ParseCulture = CultureInfo.InvariantCulture;
|
|
private readonly ParserArguments _arguments;
|
|
// Working context implementation
|
//ParameterExpression it;
|
|
private int _parsePosition;
|
private readonly string _expressionText;
|
private readonly int _expressionTextLength;
|
private char _parseChar;
|
private Token _token;
|
|
private readonly MemberFinder _memberFinder;
|
|
private readonly DefaultNumberType _defaultNumberType;
|
|
private Parser(ParserArguments arguments)
|
{
|
_arguments = arguments;
|
|
_memberFinder = new MemberFinder(arguments);
|
|
_defaultNumberType = arguments.Settings.DefaultNumberType;
|
|
_expressionText = arguments.ExpressionText ?? string.Empty;
|
_expressionTextLength = _expressionText.Length;
|
SetTextPos(0);
|
NextToken();
|
}
|
|
private Expression Parse()
|
{
|
var expr = ParseExpressionSegment(_arguments.ExpressionReturnType);
|
|
ValidateToken(TokenId.End, ErrorMessages.SyntaxError);
|
return expr;
|
}
|
|
private Expression ParseExpressionSegment(Type returnType)
|
{
|
var errorPos = _token.pos;
|
var expression = ParseExpressionSegment();
|
|
if (returnType != typeof(void))
|
{
|
return GenerateConversion(expression, returnType, errorPos);
|
}
|
|
return expression;
|
}
|
|
private Expression ParseExpressionSegment()
|
{
|
// The following methods respect the operator precedence as defined in
|
// MSDN C# "Operator precedence and associativity"
|
// https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/
|
|
try
|
{
|
if (_arguments.Settings.LambdaExpressions)
|
{
|
var lambdaExpr = ParseLambdaExpression();
|
if (lambdaExpr != null)
|
return lambdaExpr;
|
}
|
|
return ParseAssignment();
|
}
|
catch (InvalidOperationException ex)
|
{
|
throw WrapWithParseException(_token.pos, ErrorMessages.InvalidOperation, ex);
|
}
|
}
|
|
// => operator
|
private Expression ParseLambdaExpression()
|
{
|
// in case the expression is not a lambda, we have to restart parsing
|
var originalPos = _token.pos;
|
var isLambda = false;
|
try
|
{
|
var parameters = ParseLambdaParameterList();
|
ValidateToken(TokenId.LambdaArrow);
|
|
var startExpr = _parsePosition;
|
|
// decide where the lambda expression body ends:
|
// it's the first unmatched parenthesis, curly bracket, of comma
|
var parenCount = 0;
|
var inLambdaBody = true;
|
while (inLambdaBody)
|
{
|
NextToken();
|
if (_token.id == TokenId.End)
|
inLambdaBody = false;
|
if (_token.id == TokenId.OpenParen || _token.id == TokenId.OpenCurlyBracket)
|
parenCount++;
|
if (_token.id == TokenId.CloseParen || _token.id == TokenId.CloseCurlyBracket)
|
parenCount--;
|
|
// lambda is a function parameter
|
if (parenCount == 0 && _token.id == TokenId.Comma)
|
inLambdaBody = false;
|
|
// body closure
|
if (parenCount < 0)
|
inLambdaBody = false;
|
}
|
|
var lambdaBodyExp = _expressionText.Substring(startExpr, _token.pos - startExpr);
|
isLambda = true;
|
return new InterpreterExpression(_arguments, lambdaBodyExp, parameters);
|
}
|
catch (ParseException)
|
{
|
if (isLambda)
|
{
|
throw;
|
}
|
// not a lambda, return to the saved position
|
SetTextPos(originalPos);
|
NextToken();
|
|
return null;
|
}
|
}
|
|
private ParameterWithPosition[] ParseLambdaParameterList()
|
{
|
var hasOpenParen = _token.id == TokenId.OpenParen;
|
if (hasOpenParen)
|
NextToken();
|
|
var parameters = _token.id != TokenId.CloseParen ? ParseLambdaParameters() : new ParameterWithPosition[0];
|
if (hasOpenParen)
|
{
|
ValidateToken(TokenId.CloseParen, ErrorMessages.CloseParenOrCommaExpected);
|
NextToken();
|
}
|
|
if (!hasOpenParen && parameters.Length > 1)
|
throw new ParseException(ErrorMessages.MultipleLambdaParametersWithoutBrace, _parsePosition);
|
|
return parameters;
|
}
|
|
private ParameterWithPosition[] ParseLambdaParameters()
|
{
|
var argList = new List<ParameterWithPosition>();
|
while (true)
|
{
|
argList.Add(ParseLambdaParameter());
|
if (_token.id != TokenId.Comma) break;
|
NextToken();
|
}
|
return argList.ToArray();
|
}
|
|
private ParameterWithPosition ParseLambdaParameter()
|
{
|
ValidateToken(TokenId.Identifier);
|
var name = _token.text;
|
|
var pos = _token.pos;
|
if (TryParseKnownType(name, out var type))
|
{
|
ValidateToken(TokenId.Identifier);
|
name = _token.text;
|
}
|
else
|
{
|
type = typeof(object);
|
}
|
|
NextToken();
|
return new ParameterWithPosition(pos, name, type);
|
}
|
|
// = operator
|
private Expression ParseAssignment()
|
{
|
var left = ParseConditional();
|
if (_token.id == TokenId.Equal)
|
{
|
if (!_arguments.Settings.AssignmentOperators.HasFlag(AssignmentOperators.AssignmentEqual))
|
throw new AssignmentOperatorDisabledException("=", _token.pos);
|
|
Func<Expression, Expression> assignLeftDynamic = null;
|
if (!IsWritable(left) && !IsDynamicWritable(left, out assignLeftDynamic))
|
throw ParseException.Create(_token.pos, ErrorMessages.ExpressionMustBeWritable);
|
|
NextToken();
|
var right = ParseAssignment();
|
|
var promoted = ExpressionUtils.PromoteExpression(right, left.Type);
|
if (promoted == null)
|
throw ParseException.Create(_token.pos, ErrorMessages.CannotConvertValue,
|
TypeUtils.GetTypeName(right.Type), TypeUtils.GetTypeName(left.Type));
|
|
left = assignLeftDynamic == null
|
? Expression.Assign(left, promoted)
|
: assignLeftDynamic(promoted);
|
}
|
return left;
|
}
|
|
// ?: operator
|
private Expression ParseConditional()
|
{
|
var errorPos = _token.pos;
|
var expr = ParseConditionalOr();
|
if (_token.id == TokenId.QuestionQuestion)
|
{
|
NextToken();
|
var exprRight = ParseExpressionSegment();
|
if (TypeUtils.IsNullableType(expr.Type))
|
{
|
// expr.HasValue ? expr.Value : exprRight
|
expr = GenerateConditional(GenerateGetNullableHasValue(expr), GenerateGetNullableValue(expr), exprRight, errorPos);
|
}
|
else
|
{
|
// expr == null ? exprRight : expr
|
expr = GenerateConditional(GenerateEqual(expr, ParserConstants.NullLiteralExpression), exprRight, expr, errorPos);
|
}
|
}
|
else if (_token.id == TokenId.Question)
|
{
|
NextToken();
|
var expr1 = ParseExpressionSegment();
|
ValidateToken(TokenId.Colon, ErrorMessages.ColonExpected);
|
NextToken();
|
var expr2 = ParseExpressionSegment();
|
expr = GenerateConditional(expr, expr1, expr2, errorPos);
|
}
|
return expr;
|
}
|
|
// || operator
|
private Expression ParseConditionalOr()
|
{
|
var left = ParseConditionalAnd();
|
while (_token.id == TokenId.DoubleBar)
|
{
|
NextToken();
|
var right = ParseConditionalAnd();
|
CheckAndPromoteOperands(ParseSignatures.LogicalSignatures, ref left, ref right);
|
left = GenerateBinary(ExpressionType.OrElse, left, right);
|
}
|
return left;
|
}
|
|
// && operator
|
private Expression ParseConditionalAnd()
|
{
|
var left = ParseLogicalOr();
|
while (_token.id == TokenId.DoubleAmphersand)
|
{
|
NextToken();
|
var right = ParseLogicalOr();
|
CheckAndPromoteOperands(ParseSignatures.LogicalSignatures, ref left, ref right);
|
left = GenerateBinary(ExpressionType.AndAlso, left, right);
|
}
|
return left;
|
}
|
|
// | operator
|
private Expression ParseLogicalOr()
|
{
|
var left = ParseLogicalXor();
|
while (_token.id == TokenId.Bar)
|
{
|
NextToken();
|
var right = ParseLogicalXor();
|
CheckAndPromoteOperands(ParseSignatures.LogicalSignatures, ref left, ref right);
|
left = GenerateBinary(ExpressionType.Or, left, right);
|
}
|
return left;
|
}
|
|
// ^ operator
|
private Expression ParseLogicalXor()
|
{
|
var left = ParseLogicalAnd();
|
while (_token.id == TokenId.Caret)
|
{
|
NextToken();
|
var right = ParseLogicalAnd();
|
CheckAndPromoteOperands(ParseSignatures.LogicalSignatures, ref left, ref right);
|
left = GenerateBinary(ExpressionType.ExclusiveOr, left, right);
|
}
|
return left;
|
}
|
|
// & operator
|
private Expression ParseLogicalAnd()
|
{
|
var left = ParseComparison();
|
while (_token.id == TokenId.Amphersand)
|
{
|
NextToken();
|
var right = ParseComparison();
|
CheckAndPromoteOperands(ParseSignatures.LogicalSignatures, ref left, ref right);
|
left = GenerateBinary(ExpressionType.And, left, right);
|
}
|
return left;
|
}
|
|
// ==, !=, >, >=, <, <= operators
|
private Expression ParseComparison()
|
{
|
var left = ParseTypeTesting();
|
while (_token.id == TokenId.DoubleEqual || _token.id == TokenId.ExclamationEqual ||
|
_token.id == TokenId.GreaterThan || _token.id == TokenId.GreaterThanEqual ||
|
_token.id == TokenId.LessThan || _token.id == TokenId.LessThanEqual)
|
{
|
var op = _token;
|
NextToken();
|
var right = ParseShift();
|
var isEquality = op.id == TokenId.DoubleEqual || op.id == TokenId.ExclamationEqual;
|
|
//if (isEquality && !left.Type.IsValueType && !right.Type.IsValueType)
|
//{
|
// if (left.Type != right.Type)
|
// {
|
// if (left.Type.IsAssignableFrom(right.Type))
|
// {
|
// right = Expression.Convert(right, left.Type);
|
// }
|
// else if (right.Type.IsAssignableFrom(left.Type))
|
// {
|
// left = Expression.Convert(left, right.Type);
|
// }
|
// else
|
// {
|
// throw ParseException.Create(op.pos, ErrorMessages.IncompatibleOperands,
|
// op.text, TypeUtils.GetTypeName(left.Type), TypeUtils.GetTypeName(right.Type));
|
// }
|
// }
|
//}
|
//else if (IsEnumType(left.Type) || IsEnumType(right.Type))
|
//{
|
// if (left.Type != right.Type)
|
// {
|
// Expression e;
|
// if ((e = PromoteExpression(right, left.Type, true)) != null)
|
// {
|
// right = e;
|
// }
|
// else if ((e = PromoteExpression(left, right.Type, true)) != null)
|
// {
|
// left = e;
|
// }
|
// else
|
// {
|
// throw ParseException.Create(op.pos, ErrorMessages.IncompatibleOperands,
|
// op.text, TypeUtils.GetTypeName(left.Type), TypeUtils.GetTypeName(right.Type));
|
// }
|
// }
|
//}
|
//else
|
//{
|
// CheckAndPromoteOperands(isEquality ? ParseSignatures.EqualitySignatures : ParseSignatures.RelationalSignatures,
|
// op.text, ref left, ref right, op.pos);
|
//}
|
|
CheckAndPromoteOperands(
|
isEquality ? ParseSignatures.EqualitySignatures : ParseSignatures.RelationalSignatures,
|
ref left,
|
ref right);
|
|
switch (op.id)
|
{
|
case TokenId.DoubleEqual:
|
left = GenerateEqual(left, right);
|
break;
|
case TokenId.ExclamationEqual:
|
left = GenerateNotEqual(left, right);
|
break;
|
case TokenId.GreaterThan:
|
left = GenerateGreaterThan(left, right);
|
break;
|
case TokenId.GreaterThanEqual:
|
left = GenerateGreaterThanEqual(left, right);
|
break;
|
case TokenId.LessThan:
|
left = GenerateLessThan(left, right);
|
break;
|
case TokenId.LessThanEqual:
|
left = GenerateLessThanEqual(left, right);
|
break;
|
}
|
}
|
return left;
|
}
|
|
// is, as operators
|
private Expression ParseTypeTesting()
|
{
|
var left = ParseShift();
|
while (_token.text == ParserConstants.KeywordIs
|
|| _token.text == ParserConstants.KeywordAs)
|
{
|
var typeOperator = _token.text;
|
|
var op = _token;
|
NextToken();
|
|
if (!TryParseKnownType(_token.text, out var knownType))
|
throw ParseException.Create(op.pos, ErrorMessages.TypeIdentifierExpected);
|
|
if (typeOperator == ParserConstants.KeywordIs)
|
left = Expression.TypeIs(left, knownType);
|
else if (typeOperator == ParserConstants.KeywordAs)
|
left = Expression.TypeAs(left, knownType);
|
else
|
throw ParseException.Create(_token.pos, ErrorMessages.SyntaxError);
|
}
|
|
return left;
|
}
|
|
// +, -, & operators
|
private Expression ParseAdditive()
|
{
|
var left = ParseMultiplicative();
|
while (_token.id == TokenId.Plus || _token.id == TokenId.Minus)
|
{
|
var op = _token;
|
NextToken();
|
var right = ParseMultiplicative();
|
switch (op.id)
|
{
|
case TokenId.Plus:
|
if (left.Type == typeof(string) || right.Type == typeof(string))
|
{
|
left = GenerateStringConcat(left, right);
|
}
|
else
|
{
|
CheckAndPromoteOperands(ParseSignatures.AddSignatures, ref left, ref right);
|
left = GenerateBinary(ExpressionType.Add, left, right);
|
}
|
break;
|
case TokenId.Minus:
|
CheckAndPromoteOperands(ParseSignatures.SubtractSignatures, ref left, ref right);
|
left = GenerateBinary(ExpressionType.Subtract, left, right);
|
break;
|
}
|
}
|
return left;
|
}
|
|
// << , >> operators
|
private Expression ParseShift()
|
{
|
var left = ParseAdditive();
|
while (IsShiftOperator(out var shiftType))
|
{
|
NextToken();
|
var right = ParseAdditive();
|
CheckAndPromoteOperands(ParseSignatures.ShiftSignatures, ref left, ref right);
|
left = GenerateBinary(shiftType, left, right);
|
}
|
return left;
|
}
|
|
/// <summary>
|
/// Returns true if and only if the current token is in fact a shift operator.
|
/// In that case <paramref name="shiftType"/> is set to the proper expression type.
|
/// If the function returns false, <paramref name="shiftType"/> shouldn't be used.
|
/// </summary>
|
public bool IsShiftOperator(out ExpressionType shiftType)
|
{
|
// >> is not a token, because it conflicts with generics such as List<List<int>>
|
if (_token.id == TokenId.GreaterThan && _parseChar == '>')
|
{
|
NextToken(); // consume next >
|
shiftType = ExpressionType.RightShift;
|
return true;
|
}
|
// << could be a token, but is not for symmetry
|
else if (_token.id == TokenId.LessThan && _parseChar == '<')
|
{
|
NextToken(); // consume next <
|
shiftType = ExpressionType.LeftShift;
|
return true;
|
}
|
|
// dummy expression type that shouldn't be used
|
shiftType = ExpressionType.DebugInfo;
|
return false;
|
}
|
|
// *, /, % operators
|
private Expression ParseMultiplicative()
|
{
|
var left = ParseUnary();
|
while (_token.id == TokenId.Asterisk || _token.id == TokenId.Slash ||
|
_token.id == TokenId.Percent)
|
{
|
var op = _token;
|
NextToken();
|
var right = ParseUnary();
|
|
CheckAndPromoteOperands(ParseSignatures.ArithmeticSignatures, ref left, ref right);
|
|
switch (op.id)
|
{
|
case TokenId.Asterisk:
|
left = GenerateBinary(ExpressionType.Multiply, left, right);
|
break;
|
case TokenId.Slash:
|
left = GenerateBinary(ExpressionType.Divide, left, right);
|
break;
|
case TokenId.Percent:
|
left = GenerateBinary(ExpressionType.Modulo, left, right);
|
break;
|
}
|
}
|
return left;
|
}
|
|
// +,-, ! unary operators
|
private Expression ParseUnary()
|
{
|
if (_token.id == TokenId.Minus || _token.id == TokenId.Plus ||
|
_token.id == TokenId.Exclamation || _token.id == TokenId.Tilde)
|
{
|
var op = _token;
|
NextToken();
|
if (_token.id == TokenId.IntegerLiteral ||
|
_token.id == TokenId.RealLiteral)
|
{
|
if (op.id == TokenId.Minus)
|
{
|
_token.text = "-" + _token.text;
|
_token.pos = op.pos;
|
return ParsePrimary();
|
}
|
|
if (op.id == TokenId.Plus)
|
{
|
_token.text = "+" + _token.text;
|
_token.pos = op.pos;
|
return ParsePrimary();
|
}
|
}
|
var expr = ParseUnary();
|
if (op.id == TokenId.Minus)
|
{
|
CheckAndPromoteOperand(ParseSignatures.NegationSignatures, ref expr);
|
expr = GenerateUnary(ExpressionType.Negate, expr);
|
}
|
else if (op.id == TokenId.Plus)
|
{
|
|
}
|
else if (op.id == TokenId.Exclamation)
|
{
|
CheckAndPromoteOperand(ParseSignatures.NotSignatures, ref expr);
|
expr = GenerateUnary(ExpressionType.Not, expr);
|
}
|
else if (op.id == TokenId.Tilde)
|
{
|
CheckAndPromoteOperand(ParseSignatures.BitwiseComplementSignatures, ref expr);
|
expr = GenerateUnary(ExpressionType.OnesComplement, expr);
|
}
|
return expr;
|
}
|
return ParsePrimary();
|
}
|
|
private Expression GenerateUnary(ExpressionType unaryType, Expression expr)
|
{
|
if (IsDynamicExpression(expr))
|
return GenerateUnaryDynamic(unaryType, expr);
|
|
// enum unary operations are not resolved properly by Linq
|
var unaryOps = new[] { ExpressionType.OnesComplement };
|
if (expr.Type.IsEnum && unaryOps.Contains(unaryType))
|
return GenerateUnaryEnums(unaryType, expr);
|
|
// find the overloaded unary operator
|
string opName;
|
switch (unaryType)
|
{
|
case ExpressionType.Negate: opName = "op_UnaryNegation"; break;
|
case ExpressionType.Not: opName = "op_LogicalNot"; break;
|
case ExpressionType.OnesComplement: opName = "op_OnesComplement"; break;
|
default: opName = null; break;
|
}
|
|
var applicableMethod = FindUnaryOperator(opName, expr);
|
|
MethodInfo operatorMethod = null;
|
if (applicableMethod != null)
|
{
|
operatorMethod = applicableMethod.MethodBase as MethodInfo;
|
expr = applicableMethod.PromotedParameters[0];
|
}
|
|
// if no operator was found, the default Linq resolution will occur
|
return Expression.MakeUnary(unaryType, expr, null, operatorMethod);
|
}
|
|
private Expression GenerateUnaryEnums(ExpressionType unaryType, Expression expr)
|
{
|
var enumType = expr.Type;
|
var underlyingType = enumType.GetEnumUnderlyingType();
|
expr = Expression.Convert(expr, underlyingType);
|
return Expression.MakeUnary(unaryType, expr, enumType);
|
}
|
|
private Expression GenerateUnaryDynamic(ExpressionType unaryType, Expression expr)
|
{
|
var binder = Microsoft.CSharp.RuntimeBinder.Binder.UnaryOperation(
|
CSharpBinderFlags.None,
|
unaryType,
|
typeof(Parser),
|
new CSharpArgumentInfo[] {
|
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)}
|
);
|
|
return Expression.Dynamic(binder, typeof(object), expr);
|
}
|
|
private MethodData FindUnaryOperator(string operatorName, Expression expr)
|
{
|
if (operatorName == null)
|
return null;
|
|
var errorPos = _token.pos;
|
var type = expr.Type;
|
var args = new[] { expr };
|
|
// try to find the user defined operator on both operands
|
var applicableMethods = _memberFinder.FindMethods(type, operatorName, true, args);
|
if (applicableMethods.Count > 1)
|
throw ParseException.Create(errorPos, ErrorMessages.AmbiguousUnaryOperatorInvocation, operatorName, TypeUtils.GetTypeName(type));
|
|
MethodData userDefinedOperator = null;
|
if (applicableMethods.Count == 1)
|
userDefinedOperator = applicableMethods[0];
|
|
return userDefinedOperator;
|
}
|
|
private Expression ParsePrimary()
|
{
|
var tokenPos = _token.pos;
|
var expr = ParsePrimaryStart();
|
while (true)
|
{
|
if (_token.id == TokenId.Dot)
|
{
|
NextToken();
|
expr = ParseMemberAccess(expr);
|
}
|
// special case for ?. and ?[ operators
|
else if (_token.id == TokenId.Question && (_parseChar == '.' || _parseChar == '['))
|
{
|
NextToken();
|
|
if (_token.id == TokenId.Dot)
|
{
|
NextToken();
|
|
// ?. operator changes value types to nullable types
|
// the member access should be resolved on the underlying type
|
var memberAccess = GenerateNullableTypeConversion(ParseMemberAccess(GenerateGetNullableValue(expr)));
|
var nullExpr = ParserConstants.NullLiteralExpression;
|
CheckAndPromoteOperands(ParseSignatures.EqualitySignatures, ref expr, ref nullExpr);
|
expr = GenerateConditional(GenerateEqual(expr, nullExpr), ParserConstants.NullLiteralExpression, memberAccess, _token.pos);
|
}
|
else if (_token.id == TokenId.OpenBracket)
|
{
|
// ?[ operator changes value types to nullable types
|
// the member access should be resolved on the underlying type
|
var elementAccess = GenerateNullableTypeConversion(ParseElementAccess(GenerateGetNullableValue(expr)));
|
var nullExpr = ParserConstants.NullLiteralExpression;
|
CheckAndPromoteOperands(ParseSignatures.EqualitySignatures, ref expr, ref nullExpr);
|
expr = GenerateConditional(GenerateEqual(expr, nullExpr), ParserConstants.NullLiteralExpression, elementAccess, _token.pos);
|
}
|
}
|
else if (_token.id == TokenId.OpenBracket)
|
{
|
expr = ParseElementAccess(expr);
|
}
|
else if (_token.id == TokenId.OpenParen)
|
{
|
if (expr is LambdaExpression lambda)
|
return ParseLambdaInvocation(lambda, tokenPos);
|
|
if (expr is MethodGroupExpression methodGroup)
|
expr = ParseMethodGroupInvocation(methodGroup, tokenPos);
|
else if (typeof(Delegate).IsAssignableFrom(expr.Type))
|
expr = ParseDelegateInvocation(expr, tokenPos);
|
else
|
throw ParseException.Create(tokenPos, ErrorMessages.InvalidMethodCall, TypeUtils.GetTypeName(expr.Type));
|
}
|
else
|
{
|
break;
|
}
|
}
|
return expr;
|
}
|
|
/// <summary>
|
/// Generate a call to the HasValue property of the Nullable type */
|
/// </summary>
|
private Expression GenerateGetNullableHasValue(Expression expr)
|
{
|
if (!TypeUtils.IsNullableType(expr.Type))
|
return expr;
|
return GeneratePropertyOrFieldExpression(expr.Type, expr, _token.pos, "HasValue");
|
}
|
|
/// <summary>
|
/// Generate a call to the Value property of the Nullable type */
|
/// </summary>
|
private Expression GenerateGetNullableValue(Expression expr)
|
{
|
if (!TypeUtils.IsNullableType(expr.Type))
|
return expr;
|
return GeneratePropertyOrFieldExpression(expr.Type, expr, _token.pos, "Value");
|
}
|
|
private Expression ParsePrimaryStart()
|
{
|
switch (_token.id)
|
{
|
case TokenId.Identifier:
|
return ParseIdentifier();
|
case TokenId.CharLiteral:
|
return ParseCharLiteral();
|
case TokenId.StringLiteral:
|
return ParseStringLiteral();
|
case TokenId.IntegerLiteral:
|
return ParseIntegerLiteral();
|
case TokenId.RealLiteral:
|
return ParseRealLiteral();
|
case TokenId.OpenParen:
|
return ParseParenExpression();
|
case TokenId.End:
|
return Expression.Empty();
|
default:
|
throw ParseException.Create(_token.pos, ErrorMessages.ExpressionExpected);
|
}
|
}
|
|
private Expression ParseCharLiteral()
|
{
|
ValidateToken(TokenId.CharLiteral);
|
var s = _token.text.Substring(1, _token.text.Length - 2);
|
|
s = EvalEscapeStringLiteral(s);
|
|
if (s.Length != 1)
|
throw ParseException.Create(_token.pos, ErrorMessages.InvalidCharacterLiteral);
|
|
NextToken();
|
return CreateLiteral(s[0]);
|
}
|
|
private Expression ParseStringLiteral()
|
{
|
ValidateToken(TokenId.StringLiteral);
|
var s = _token.text.Substring(1, _token.text.Length - 2);
|
|
s = EvalEscapeStringLiteral(s);
|
|
//int start = 0;
|
//while (true)
|
//{
|
// int i = s.IndexOf(quote, start);
|
// if (i < 0)
|
// break;
|
// s = s.Remove(i, 1);
|
// start = i + 1;
|
//}
|
|
NextToken();
|
return CreateLiteral(s);
|
}
|
|
private string EvalEscapeStringLiteral(string source)
|
{
|
// most of the string will not have escape sequences; avoid allocation if possible
|
if (source.IndexOf('\\') < 0)
|
return source;
|
|
var builder = new StringBuilder(source.Length);
|
for (var i = 0; i < source.Length; i++)
|
{
|
var c = source[i];
|
if (c == '\\')
|
{
|
if ((i + 1) == source.Length)
|
throw ParseException.Create(_token.pos, ErrorMessages.InvalidEscapeSequence);
|
|
builder.Append(EvalEscapeChar(source[++i]));
|
}
|
else
|
builder.Append(c);
|
}
|
|
return builder.ToString();
|
}
|
|
private char EvalEscapeChar(char source)
|
{
|
switch (source)
|
{
|
case '\'':
|
case '"':
|
case '\\':
|
return source;
|
case '0':
|
return '\0';
|
case 'a':
|
return '\a';
|
case 'b':
|
return '\b';
|
case 'f':
|
return '\f';
|
case 'n':
|
return '\n';
|
case 'r':
|
return '\r';
|
case 't':
|
return '\t';
|
case 'v':
|
return '\v';
|
default:
|
throw ParseException.Create(_token.pos, ErrorMessages.InvalidEscapeSequence);
|
}
|
}
|
|
private Expression ParseIntegerLiteral()
|
{
|
ValidateToken(TokenId.IntegerLiteral);
|
var text = _token.text;
|
|
var isUnsigned = false;
|
var isLong = false;
|
var numberEnd = text.Length - 1;
|
for (; numberEnd >= 0; numberEnd--)
|
{
|
var chr = text[numberEnd];
|
if (chr == 'U' || chr == 'u')
|
isUnsigned = true;
|
else if (chr == 'L' || chr == 'l')
|
isLong = true;
|
else
|
break;
|
}
|
|
text = text.Substring(0, numberEnd + 1);
|
|
// No suffix found, verify if DefaultNumberType.Long is specified
|
if (_defaultNumberType == DefaultNumberType.Long) isLong = true;
|
|
if (text[0] != '-')
|
{
|
ulong value;
|
if (text.StartsWith("0x") || text.StartsWith("0X"))
|
{
|
var hex = text.Substring(2);
|
if (!ulong.TryParse(hex, ParseLiteralHexNumberStyle, ParseCulture, out value))
|
throw ParseException.Create(_token.pos, ErrorMessages.InvalidIntegerLiteral, text);
|
}
|
else if (text.StartsWith("0b") || text.StartsWith("0B"))
|
{
|
var binary = text.Substring(2);
|
if (string.IsNullOrEmpty(binary))
|
throw ParseException.Create(_token.pos, ErrorMessages.InvalidIntegerLiteral, text);
|
|
try
|
{
|
value = Convert.ToUInt64(binary, 2);
|
}
|
catch (FormatException ex)
|
{
|
throw WrapWithParseException(_token.pos, ErrorMessages.InvalidIntegerLiteral, ex, text);
|
}
|
}
|
else if (!ulong.TryParse(text, ParseLiteralUnsignedNumberStyle, ParseCulture, out value))
|
throw ParseException.Create(_token.pos, ErrorMessages.InvalidIntegerLiteral, text);
|
|
NextToken();
|
|
if (!isUnsigned && !isLong && value <= int.MaxValue)
|
return CreateLiteral((int)value);
|
if (!isLong && value <= uint.MaxValue)
|
return CreateLiteral((uint)value);
|
if (!isUnsigned && value <= long.MaxValue)
|
return CreateLiteral((long)value);
|
|
return CreateLiteral(value);
|
}
|
else
|
{
|
if (!long.TryParse(text, ParseLiteralNumberStyle, ParseCulture, out var value))
|
throw ParseException.Create(_token.pos, ErrorMessages.InvalidIntegerLiteral, text);
|
|
NextToken();
|
|
if (!isLong && value >= int.MinValue && value <= int.MaxValue)
|
return CreateLiteral((int)value);
|
|
return CreateLiteral(value);
|
}
|
}
|
|
private Expression ParseRealLiteral()
|
{
|
ValidateToken(TokenId.RealLiteral);
|
var text = _token.text;
|
object value = null;
|
var last = text[text.Length - 1];
|
|
if (last == 'F' || last == 'f')
|
{
|
if (float.TryParse(text.Substring(0, text.Length - 1), ParseLiteralDecimalNumberStyle, ParseCulture, out var f))
|
value = f;
|
}
|
else if (last == 'M' || last == 'm')
|
{
|
if (decimal.TryParse(text.Substring(0, text.Length - 1), ParseLiteralDecimalNumberStyle, ParseCulture, out var dc))
|
value = dc;
|
}
|
else if (last == 'D' || last == 'd')
|
{
|
if (double.TryParse(text.Substring(0, text.Length - 1), ParseLiteralDoubleNumberStyle, ParseCulture, out var d))
|
value = d;
|
}
|
else
|
{
|
// No suffix find, use DefaultNumberType settigns if specified (Double default)
|
if (_defaultNumberType == DefaultNumberType.Decimal)
|
{
|
if (decimal.TryParse(text, ParseLiteralDecimalNumberStyle, ParseCulture, out var dc))
|
value = dc;
|
}
|
else if (_defaultNumberType == DefaultNumberType.Single)
|
{
|
if (float.TryParse(text, ParseLiteralDecimalNumberStyle, ParseCulture, out var f))
|
value = f;
|
}
|
else
|
{
|
if (double.TryParse(text, ParseLiteralDoubleNumberStyle, ParseCulture, out var d))
|
value = d;
|
}
|
}
|
|
if (value == null)
|
throw ParseException.Create(_token.pos, ErrorMessages.InvalidRealLiteral, text);
|
|
NextToken();
|
|
return CreateLiteral(value);
|
}
|
|
private static Expression CreateLiteral(object value)
|
{
|
var expr = Expression.Constant(value);
|
|
return expr;
|
}
|
|
private Expression ParseParenExpression()
|
{
|
ValidateToken(TokenId.OpenParen, ErrorMessages.OpenParenExpected);
|
NextToken();
|
var innerParenthesesExpression = ParseExpressionSegment();
|
ValidateToken(TokenId.CloseParen, ErrorMessages.CloseParenOrOperatorExpected);
|
|
if (innerParenthesesExpression is ConstantExpression constExp && constExp.Value is Type constType)
|
{
|
NextToken();
|
|
// we're in a cast expression, the next expression can only be a unary expression
|
var nextExpression = ParseUnary();
|
|
// cast: (constType)nextExpression
|
return Expression.Convert(nextExpression, constType);
|
}
|
|
NextToken();
|
return innerParenthesesExpression;
|
}
|
|
private Expression ParseIdentifier()
|
{
|
ValidateToken(TokenId.Identifier);
|
|
// Working context implementation
|
//if (token.text == ParserConstants.keywordIt)
|
// return ParseIt();
|
|
if (_token.text == ParserConstants.KeywordNew)
|
return ParseNew();
|
if (_token.text == ParserConstants.KeywordTypeof)
|
return ParseTypeof();
|
if (_token.text == ParserConstants.KeywordDefault)
|
return ParseDefaultOperator();
|
|
if (_arguments.TryGetIdentifier(_token.text, out var keywordExpression))
|
{
|
NextToken();
|
return keywordExpression;
|
}
|
|
if (_arguments.TryGetParameters(_token.text, out var parameterExpression))
|
{
|
NextToken();
|
return parameterExpression;
|
}
|
|
if (TryParseKnownType(_token.text, out var knownType))
|
{
|
return ParseTypeKeyword(knownType);
|
}
|
|
var token = _token;
|
|
try
|
{
|
if (_arguments.TryGetIdentifier(LanguageConstants.This, out var thisKeywordExpression))
|
{
|
return ParseMemberAccess(thisKeywordExpression);
|
}
|
|
if (_arguments.TryGetParameters(LanguageConstants.This, out var thisParameterExpression))
|
{
|
return ParseMemberAccess(thisParameterExpression);
|
}
|
}
|
catch (ParseException)
|
{
|
// ignore
|
}
|
|
// Working context implementation
|
//if (it != null)
|
// return ParseMemberAccess(null, it);
|
|
throw new UnknownIdentifierException(token.text, token.pos);
|
}
|
|
// Working context implementation
|
//Expression ParseIt()
|
//{
|
// if (it == null)
|
// throw ParseError(ErrorMessages.NoItInScope);
|
// NextToken();
|
// return it;
|
//}
|
|
//Expression ParseIif()
|
//{
|
// int errorPos = token.pos;
|
// NextToken();
|
// Expression[] args = ParseArgumentList();
|
// if (args.Length != 3)
|
// throw ParseError(errorPos, ErrorMessages.IifRequiresThreeArgs);
|
// return GenerateConditional(args[0], args[1], args[2], errorPos);
|
//}
|
|
private Expression ParseTypeof()
|
{
|
var errorPos = _token.pos;
|
NextToken();
|
var args = ParseArgumentList();
|
if (args.Length != 1)
|
throw ParseException.Create(errorPos, ErrorMessages.TypeofRequiresOneArg);
|
|
if (!(args[0] is ConstantExpression constExp && constExp.Value is Type))
|
throw ParseException.Create(errorPos, ErrorMessages.TypeofRequiresAType);
|
|
return constExp;
|
}
|
|
private Expression ParseDefaultOperator()
|
{
|
NextToken();
|
|
ValidateToken(TokenId.OpenParen, ErrorMessages.OpenParenExpected);
|
NextToken();
|
ValidateToken(TokenId.Identifier);
|
var type = ParseKnownType();
|
ValidateToken(TokenId.CloseParen, ErrorMessages.CloseParenOrCommaExpected);
|
NextToken();
|
|
return Expression.Default(type);
|
}
|
|
private Expression GenerateConditional(Expression test, Expression expr1, Expression expr2, int errorPos)
|
{
|
if (IsDynamicExpression(test))
|
return GenerateConditionalDynamic(test, expr1, expr2, errorPos);
|
|
if (test.Type != typeof(bool))
|
throw ParseException.Create(errorPos, ErrorMessages.FirstExprMustBeBool);
|
if (expr1.Type != expr2.Type)
|
{
|
var expr1As2 = expr2 != ParserConstants.NullLiteralExpression ? ExpressionUtils.PromoteExpression(expr1, expr2.Type) : null;
|
var expr2As1 = expr1 != ParserConstants.NullLiteralExpression ? ExpressionUtils.PromoteExpression(expr2, expr1.Type) : null;
|
if (expr1As2 != null && expr2As1 == null)
|
{
|
expr1 = expr1As2;
|
}
|
else if (expr2As1 != null && expr1As2 == null)
|
{
|
expr2 = expr2As1;
|
}
|
else
|
{
|
var type1 = expr1 != ParserConstants.NullLiteralExpression ? expr1.Type.Name : "null";
|
var type2 = expr2 != ParserConstants.NullLiteralExpression ? expr2.Type.Name : "null";
|
if (expr1As2 != null)
|
throw ParseException.Create(errorPos, ErrorMessages.BothTypesConvertToOther, type1, type2);
|
|
throw ParseException.Create(errorPos, ErrorMessages.NeitherTypeConvertsToOther, type1, type2);
|
}
|
}
|
return Expression.Condition(test, expr1, expr2);
|
}
|
|
private Expression GenerateConditionalDynamic(Expression test, Expression expr1, Expression expr2, int errorPos)
|
{
|
var casted = ConvertBoolExpression(test);
|
|
return GenerateConditional(casted, expr1, expr2, errorPos);
|
}
|
|
private Expression ParseNew()
|
{
|
NextToken();
|
ValidateToken(TokenId.Identifier, ErrorMessages.IdentifierExpected);
|
|
var newType = ParseKnownType();
|
var args = new Expression[0];
|
|
if (newType.IsArray)
|
{
|
if (newType.GetArrayRank() != 1)
|
throw ParseException.Create(_token.pos, ErrorMessages.UnsupportedMultidimensionalArrays, newType);
|
|
args = ParseArrayInitializerList();
|
return Expression.NewArrayInit(newType.GetElementType(), args);
|
}
|
|
if (_token.id == TokenId.OpenParen)
|
args = ParseArgumentList();
|
else
|
{
|
// no aguments: expect an object initializer
|
ValidateToken(TokenId.OpenCurlyBracket, ErrorMessages.OpenCurlyBracketExpected);
|
}
|
|
var applicableConstructors = MethodResolution.FindBestMethod(newType.GetConstructors(), args);
|
if (applicableConstructors.Count == 0)
|
throw ParseException.Create(_token.pos, ErrorMessages.NoApplicableConstructor, newType);
|
|
if (applicableConstructors.Count > 1)
|
throw ParseException.Create(_token.pos, ErrorMessages.AmbiguousConstructorInvocation, newType);
|
|
var constructor = applicableConstructors[0];
|
var newExpr = Expression.New((ConstructorInfo)constructor.MethodBase, constructor.PromotedParameters);
|
|
if (_token.id == TokenId.OpenCurlyBracket)
|
return ParseWithObjectInitializer(newExpr, newType);
|
|
return newExpr;
|
}
|
|
private Expression[] ParseArrayInitializerList()
|
{
|
return ParseArgumentList(TokenId.OpenCurlyBracket, ErrorMessages.OpenCurlyBracketExpected,
|
TokenId.CloseCurlyBracket, ErrorMessages.CloseCurlyBracketExpected,
|
allowTrailingComma: true);
|
}
|
|
private Expression ParseWithObjectInitializer(NewExpression newExpr, Type newType)
|
{
|
ValidateToken(TokenId.OpenCurlyBracket, ErrorMessages.OpenCurlyBracketExpected);
|
NextToken();
|
var initializedInstance = ParseMemberAndInitializerList(newExpr, newType);
|
ValidateToken(TokenId.CloseCurlyBracket, ErrorMessages.CloseCurlyBracketExpected);
|
NextToken();
|
return initializedInstance;
|
}
|
|
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;
|
if (_token.id != TokenId.Identifier)
|
{
|
ParseCollectionInitalizer(newType, originalPos, bindingList, actions, instance, allowCollectionInit);
|
}
|
else
|
{
|
ParsePossibleMemberBinding(newType, 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);
|
}
|
|
private void ParsePossibleMemberBinding(Type newType, List<MemberBinding> bindingList, List<Expression> actions, ParameterExpression instance, bool allowCollectionInit)
|
{
|
ValidateToken(TokenId.Identifier, ErrorMessages.IdentifierExpected);
|
|
var propertyOrFieldName = _token.text;
|
var member = _memberFinder.FindPropertyOrField(newType, propertyOrFieldName, false);
|
var pos = _token.pos;
|
if (allowCollectionInit)
|
{
|
NextToken();
|
//new T(){Prop = 1}
|
//new T(){{variable = 2}}
|
if (_token.id == TokenId.Equal && member != null)
|
{
|
if (actions.Count > 0)
|
{
|
throw ParseException.Create(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 ParseException.Create(pos, ErrorMessages.UnknownPropertyOrField, propertyOrFieldName, TypeUtils.GetTypeName(newType));
|
}
|
NextToken();
|
|
ValidateToken(TokenId.Equal, ErrorMessages.EqualExpected);
|
NextToken();
|
|
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 ParseException.Create(_token.pos, ErrorMessages.CollectionInitializationNotSupported, newType, typeof(IEnumerable));
|
}
|
if (bindingList.Count > 0)
|
{
|
throw ParseException.Create(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 ParseException.Create(_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 ParseException.Create(_token.pos, ErrorMessages.UnableToFindAppropriateAddMethod, TypeUtils.GetTypeName(newType));
|
}
|
actions.Add(addMethod);
|
}
|
}
|
|
private Expression ParseLambdaInvocation(LambdaExpression lambda, int errorPos)
|
{
|
return ParseInvocation(lambda, errorPos, ErrorMessages.ArgsIncompatibleWithLambda);
|
}
|
|
private Expression ParseDelegateInvocation(Expression delegateExp, int errorPos)
|
{
|
return ParseInvocation(delegateExp, errorPos, ErrorMessages.ArgsIncompatibleWithDelegate);
|
}
|
|
private Expression ParseInvocation(Expression expr, int errorPos, string error)
|
{
|
var args = ParseArgumentList();
|
|
var invokeMethod = MemberFinder.FindInvokeMethod(expr.Type);
|
if (invokeMethod != null)
|
{
|
var invokeMethodData = MethodData.Gen(invokeMethod);
|
if (MethodResolution.CheckIfMethodIsApplicableAndPrepareIt(invokeMethodData, args))
|
return Expression.Invoke(expr, invokeMethodData.PromotedParameters);
|
}
|
|
throw ParseException.Create(errorPos, error);
|
}
|
|
private Expression ParseMethodGroupInvocation(MethodGroupExpression methodGroup, int errorPos)
|
{
|
var args = ParseArgumentList();
|
|
// find the best delegates that can be used with the provided arguments
|
// we can either use the method directly, or the Invoke method of the delegate
|
var usedInvokeMethod = false;
|
var candidates = methodGroup.Overloads;
|
var applicableMethods = MethodResolution.FindBestMethod(candidates.Select(_ => _.Method), args);
|
|
// no method found: retry with the delegate's method
|
// (the parameters might be different, e.g. params array, default value, etc)
|
if (applicableMethods.Count == 0)
|
{
|
usedInvokeMethod = true;
|
applicableMethods = MethodResolution.FindBestMethod(candidates.Select(_ => _.InvokeMethod), args);
|
}
|
|
if (applicableMethods.Count == 0)
|
{
|
if (args.Any(IsDynamicExpression))
|
{
|
// TODO: we could try to find the best method by using the dynamic binder
|
var candidatesWithSameArgumentCount = candidates.Where(_ => _.Method.GetParameters().Length == args.Length).ToList();
|
if (candidatesWithSameArgumentCount.Count == 1)
|
return ParseDynamicMethodGroupInvocation(candidatesWithSameArgumentCount[0].Delegate, args);
|
}
|
|
throw ParseException.Create(errorPos, ErrorMessages.ArgsIncompatibleWithDelegate);
|
}
|
|
if (applicableMethods.Count > 1)
|
throw ParseException.Create(errorPos, ErrorMessages.AmbiguousDelegateInvocation);
|
|
var applicableMethod = applicableMethods[0];
|
var usedDelegate = methodGroup.FindUsedOverload(usedInvokeMethod, applicableMethod);
|
return Expression.Invoke(Expression.Constant(usedDelegate), applicableMethod.PromotedParameters);
|
}
|
|
private Type ParseKnownType()
|
{
|
var name = _token.text;
|
var errorPos = _token.pos;
|
if (!TryParseKnownType(name, out var type))
|
throw new UnknownIdentifierException(name, errorPos);
|
|
return type;
|
}
|
|
private bool TryParseKnownType(string name, out Type type)
|
{
|
// if the type is unknown, we need to restart parsing
|
var originalPos = _token.pos;
|
|
// the name might reference a generic type, with an aliased name (e.g. List<T> = MyList instead of List`1)
|
// it can also reference a generic type for which we don't know the arity yet (and therefore the name doesn't contain the `n suffix)
|
if (_arguments.TryGetKnownType(name, out type) || _arguments.HasKnownGenericTypeDefinition(name))
|
{
|
type = ParseKnownGenericType(name, type);
|
}
|
|
type = ParseTypeModifiers(type);
|
|
if (type == null)
|
{
|
// type name couldn't be parsed: restore position
|
SetTextPos(originalPos);
|
NextToken();
|
return false;
|
}
|
|
return true;
|
}
|
|
private Type ParseKnownGenericType(string name, Type type)
|
{
|
NextToken();
|
if (_token.id == TokenId.LessThan)
|
{
|
var typeArguments = ParseTypeArgumentList();
|
var rank = typeArguments.Count;
|
|
// if no type was registered with the simple name, try the full generic name
|
if (type == null && !_arguments.TryGetKnownType(name + $"`{rank}", out type))
|
return null;
|
|
if (rank != type.GetGenericArguments().Length)
|
throw new ArgumentException(ErrorMessages.GenericArgumentCountMismatch);
|
|
// there are actual type arguments: instantiate the proper generic type
|
if (typeArguments.All(_ => _ != null))
|
type = type.MakeGenericType(typeArguments.ToArray());
|
|
NextToken();
|
}
|
|
return type;
|
}
|
|
// we found a known type identifier, check if it has some modifiers
|
private Type ParseTypeModifiers(Type type)
|
{
|
// type modifiers require the base type to be known
|
if (type == null)
|
return null;
|
|
var errorPos = _token.pos;
|
if (_token.id == TokenId.Question)
|
{
|
if (!type.IsValueType || TypeUtils.IsNullableType(type))
|
throw ParseException.Create(errorPos, ErrorMessages.TypeHasNoNullableForm, TypeUtils.GetTypeName(type));
|
type = typeof(Nullable<>).MakeGenericType(type);
|
|
NextToken();
|
type = ParseTypeModifiers(type);
|
}
|
else if (_token.id == TokenId.OpenBracket)
|
{
|
type = ParseArrayRankSpecifier(type);
|
}
|
|
return type;
|
}
|
|
private Type ParseArrayRankSpecifier(Type type)
|
{
|
ValidateToken(TokenId.OpenBracket);
|
|
// An array type of the form T[R][R1]...[Rn] is an array with rank R and an element type T[R1]...[Rn]
|
// => we need to parse all rank specifiers in one pass, and create the array from right to left
|
var ranks = new Stack<int>();
|
while (_token.id == TokenId.OpenBracket)
|
{
|
NextToken();
|
var rank = 1;
|
while (_token.id == TokenId.Comma)
|
{
|
rank++;
|
NextToken();
|
}
|
|
ValidateToken(TokenId.CloseBracket, ErrorMessages.CloseBracketOrCommaExpected);
|
ranks.Push(rank);
|
NextToken();
|
}
|
|
while (ranks.Count > 0)
|
{
|
var rank = ranks.Pop();
|
type = rank == 1 ? type.MakeArrayType() : type.MakeArrayType(rank);
|
}
|
|
return type;
|
}
|
|
private List<Type> ParseTypeArgumentList()
|
{
|
ValidateToken(TokenId.LessThan);
|
NextToken();
|
|
List<Type> args;
|
if (_token.id == TokenId.Identifier)
|
args = ParseTypeArguments();
|
else
|
{
|
var arity = ParseUnboundTypeArity();
|
args = new List<Type>(new Type[arity]);
|
}
|
|
ValidateToken(TokenId.GreaterThan, ErrorMessages.CloseTypeArgumentListExpected);
|
return args;
|
}
|
|
private List<Type> ParseTypeArguments()
|
{
|
var genericArguments = new List<Type>();
|
while (true)
|
{
|
ValidateToken(TokenId.Identifier);
|
genericArguments.Add(ParseKnownType());
|
if (_token.id != TokenId.Comma) break;
|
NextToken();
|
}
|
|
return genericArguments;
|
}
|
|
private int ParseUnboundTypeArity()
|
{
|
var rank = 1;
|
while (_token.id == TokenId.Comma)
|
{
|
rank++;
|
NextToken();
|
}
|
|
return rank;
|
}
|
|
private Expression ParseTypeKeyword(Type type)
|
{
|
//if (token.id == TokenId.OpenParen)
|
//{
|
// return ParseTypeConstructor(type, errorPos);
|
//}
|
|
if (_token.id == TokenId.CloseParen)
|
{
|
return Expression.Constant(type);
|
}
|
|
ValidateToken(TokenId.Dot, ErrorMessages.DotOrOpenParenExpected);
|
NextToken();
|
return ParseMemberAccess(type, null);
|
}
|
|
//private Expression ParseTypeConstructor(Type type, int errorPos)
|
//{
|
// Expression[] args = ParseArgumentList();
|
// MethodBase method;
|
// switch (FindBestMethod(type.GetConstructors(), args, out method))
|
// {
|
// case 0:
|
// if (args.Length == 1)
|
// return GenerateConversion(args[0], type, errorPos);
|
// throw ParseError(errorPos, ErrorMessages.NoMatchingConstructor, TypeUtils.GetTypeName(type));
|
// case 1:
|
// return Expression.New((ConstructorInfo)method, args);
|
// default:
|
// throw ParseError(errorPos, ErrorMessages.AmbiguousConstructorInvocation, TypeUtils.GetTypeName(type));
|
// }
|
//}
|
|
private Expression GenerateConversion(Expression expr, Type type, int errorPos)
|
{
|
var exprType = expr.Type;
|
if (exprType == type)
|
{
|
return expr;
|
}
|
|
//if (exprType.IsValueType && type.IsValueType)
|
//{
|
// if ((IsNullableType(exprType) || IsNullableType(type)) &&
|
// GetNonNullableType(exprType) == GetNonNullableType(type))
|
// return Expression.Convert(expr, type);
|
// if ((IsNumericType(exprType) || IsEnumType(exprType)) &&
|
// (IsNumericType(type)) || IsEnumType(type))
|
// return Expression.ConvertChecked(expr, type);
|
//}
|
|
//if (exprType.IsAssignableFrom(type) || type.IsAssignableFrom(exprType) ||
|
// exprType.IsInterface || type.IsInterface)
|
//{
|
// return Expression.Convert(expr, type);
|
//}
|
|
// generic type was not fully resolved; try to find a type in the inheritance hierarchy
|
// that matches the generic type definition
|
if (type.ContainsGenericParameters)
|
{
|
var typeDef = type.GetGenericTypeDefinition();
|
var interfaces = exprType.GetInterfaces().ToList();
|
for (var nextType = exprType.BaseType; nextType != null; nextType = nextType.BaseType)
|
{
|
interfaces.Add(nextType);
|
}
|
|
foreach (var inherited in interfaces)
|
{
|
if (inherited.IsGenericType && inherited.GetGenericTypeDefinition() == typeDef)
|
type = inherited;
|
}
|
}
|
|
try
|
{
|
if (expr is InterpreterExpression ie)
|
{
|
return ie.EvalAs(type);
|
}
|
|
return Expression.ConvertChecked(expr, type);
|
}
|
catch (InvalidOperationException)
|
{
|
throw ParseException.Create(errorPos, ErrorMessages.CannotConvertValue,
|
TypeUtils.GetTypeName(exprType), TypeUtils.GetTypeName(type));
|
}
|
}
|
|
private Expression ParseMemberAccess(Expression instance)
|
{
|
return ParseMemberAccess(null, instance);
|
}
|
|
private Expression ParseMemberAccess(Type type, Expression instance)
|
{
|
if (instance != null) type = instance.Type;
|
var errorPos = _token.pos;
|
var id = GetIdentifier();
|
NextToken();
|
if (_token.id == TokenId.OpenParen)
|
return ParseMethodInvocation(type, instance, errorPos, id);
|
|
return GeneratePropertyOrFieldExpression(type, instance, errorPos, id);
|
}
|
|
private Expression GeneratePropertyOrFieldExpression(Type type, Expression instance, int errorPos, string propertyOrFieldName)
|
{
|
var member = _memberFinder.FindPropertyOrField(type, propertyOrFieldName, instance == null);
|
if (member != null)
|
{
|
return member is PropertyInfo ?
|
Expression.Property(instance, (PropertyInfo)member) :
|
Expression.Field(instance, (FieldInfo)member);
|
}
|
|
if (TypeUtils.IsDynamicType(type) || IsDynamicExpression(instance))
|
return ParseDynamicProperty(instance, propertyOrFieldName);
|
|
throw ParseException.Create(errorPos, ErrorMessages.UnknownPropertyOrField, propertyOrFieldName, TypeUtils.GetTypeName(type));
|
}
|
|
private Expression ParseMethodInvocation(Type type, Expression instance, int errorPos, string methodName)
|
{
|
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)
|
{
|
methodInvocationExpression = ParseExtensionMethodInvocation(type, instance, errorPos, methodName, args);
|
}
|
|
if (methodInvocationExpression != null)
|
return methodInvocationExpression;
|
|
if (TypeUtils.IsDynamicType(type) || IsDynamicExpression(instance) || args.Any(IsDynamicExpression))
|
return ParseDynamicMethodInvocation(type, instance, methodName, args);
|
|
throw new NoApplicableMethodException(methodName, TypeUtils.GetTypeName(type), errorPos);
|
}
|
|
private Expression ParseExtensionMethodInvocation(Type type, Expression instance, int errorPos, string methodName, Expression[] args)
|
{
|
var extensionMethodsArguments = new Expression[args.Length + 1];
|
extensionMethodsArguments[0] = instance;
|
args.CopyTo(extensionMethodsArguments, 1);
|
|
var extensionMethods = _memberFinder.FindExtensionMethods(methodName, extensionMethodsArguments);
|
if (extensionMethods.Count > 1)
|
throw ParseException.Create(errorPos, ErrorMessages.AmbiguousMethodInvocation, methodName, TypeUtils.GetTypeName(type));
|
|
if (extensionMethods.Count == 1)
|
{
|
var method = extensionMethods[0];
|
|
return Expression.Call((MethodInfo)method.MethodBase, method.PromotedParameters);
|
}
|
|
return null;
|
}
|
|
private Expression ParseNormalMethodInvocation(Type type, Expression instance, int errorPos, string methodName, Expression[] args)
|
{
|
var applicableMethods = _memberFinder.FindMethods(type, methodName, instance == null, args);
|
if (applicableMethods.Count > 1)
|
throw ParseException.Create(errorPos, ErrorMessages.AmbiguousMethodInvocation, methodName, TypeUtils.GetTypeName(type));
|
|
if (applicableMethods.Count == 1)
|
{
|
var method = applicableMethods[0];
|
|
return Expression.Call(instance, (MethodInfo)method.MethodBase, method.PromotedParameters);
|
}
|
|
return null;
|
}
|
|
//static Type FindGenericType(Type generic, Type type)
|
//{
|
// while (type != null && type != typeof(object))
|
// {
|
// if (type.IsGenericType && type.GetGenericTypeDefinition() == generic) return type;
|
// if (generic.IsInterface)
|
// {
|
// foreach (Type intfType in type.GetInterfaces())
|
// {
|
// Type found = FindGenericType(generic, intfType);
|
// if (found != null) return found;
|
// }
|
// }
|
// type = type.BaseType;
|
// }
|
// return null;
|
//}
|
|
//Expression ParseAggregate(Expression instance, Type elementType, string methodName, int errorPos)
|
//{
|
// ParameterExpression outerIt = it;
|
// ParameterExpression innerIt = Expression.Parameter(elementType, "");
|
// it = innerIt;
|
// Expression[] args = ParseArgumentList();
|
// it = outerIt;
|
// MethodBase signature;
|
// if (FindMethod(typeof(IEnumerableSignatures), methodName, false, args, out signature) != 1)
|
// throw ParseError(errorPos, ErrorMessages.NoApplicableAggregate, methodName);
|
// Type[] typeArgs;
|
// if (signature.Name == "Min" || signature.Name == "Max")
|
// {
|
// typeArgs = new Type[] { elementType, args[0].Type };
|
// }
|
// else
|
// {
|
// typeArgs = new Type[] { elementType };
|
// }
|
// if (args.Length == 0)
|
// {
|
// args = new Expression[] { instance };
|
// }
|
// else
|
// {
|
// args = new Expression[] { instance, Expression.Lambda(args[0], innerIt) };
|
// }
|
// return Expression.Call(typeof(Enumerable), signature.Name, typeArgs, args);
|
//}
|
|
private static Expression ParseDynamicProperty(Expression instance, string propertyOrFieldName)
|
{
|
return Expression.Dynamic(new LateGetMemberCallSiteBinder(propertyOrFieldName), typeof(object), instance);
|
}
|
|
private static Expression ParseDynamicMethodInvocation(Type type, Expression instance, string methodName, Expression[] args)
|
{
|
var argsDynamic = args.ToList();
|
var isStatic = instance == null;
|
argsDynamic.Insert(0, !isStatic ? instance : Expression.Constant(type));
|
return Expression.Dynamic(new LateInvokeMethodCallSiteBinder(methodName, isStatic), typeof(object), argsDynamic);
|
}
|
|
private Expression ParseDynamicMethodGroupInvocation(Delegate @delegate, Expression[] args)
|
{
|
var argsDynamic = args.ToList();
|
argsDynamic.Insert(0, Expression.Constant(@delegate));
|
return Expression.Dynamic(new LateInvokeDelegateCallSiteBinder(), typeof(object), argsDynamic);
|
}
|
|
private static Expression ParseDynamicIndex(Expression instance, Expression[] args)
|
{
|
var argsDynamic = args.ToList();
|
argsDynamic.Insert(0, instance);
|
return Expression.Dynamic(new LateGetIndexCallSiteBinder(), typeof(object), argsDynamic);
|
}
|
|
private Expression[] ParseArgumentList(TokenId openToken, string missingOpenTokenMsg,
|
TokenId closeToken, string missingCloseTokenMsg,
|
bool allowTrailingComma = false)
|
{
|
ValidateToken(openToken, missingOpenTokenMsg);
|
NextToken();
|
var argList = new List<Expression>();
|
while (_token.id != closeToken)
|
{
|
argList.Add(ParseExpressionSegment());
|
if (_token.id != TokenId.Comma) break;
|
NextToken();
|
if (!allowTrailingComma && _token.id == closeToken)
|
throw ParseException.Create(_token.pos, missingCloseTokenMsg);
|
}
|
ValidateToken(closeToken, missingCloseTokenMsg);
|
NextToken();
|
return argList.ToArray();
|
}
|
|
private Expression[] ParseArgumentList()
|
{
|
return ParseArgumentList(TokenId.OpenParen, ErrorMessages.OpenParenExpected,
|
TokenId.CloseParen, ErrorMessages.CloseParenOrCommaExpected);
|
}
|
|
private Expression ParseElementAccess(Expression expr)
|
{
|
var errorPos = _token.pos;
|
var args = ParseArgumentList(TokenId.OpenBracket, ErrorMessages.OpenParenExpected,
|
TokenId.CloseBracket, ErrorMessages.CloseBracketOrCommaExpected);
|
if (expr.Type.IsArray)
|
{
|
if (expr.Type.GetArrayRank() != args.Length)
|
throw ParseException.Create(errorPos, ErrorMessages.IncorrectNumberOfIndexes);
|
|
for (var i = 0; i < args.Length; i++)
|
{
|
args[i] = ExpressionUtils.PromoteExpression(args[i], typeof(int));
|
if (args[i] == null)
|
throw ParseException.Create(errorPos, ErrorMessages.InvalidIndex);
|
}
|
|
return Expression.ArrayAccess(expr, args);
|
}
|
|
if (TypeUtils.IsDynamicType(expr.Type) || IsDynamicExpression(expr))
|
return ParseDynamicIndex(expr, args);
|
|
var applicableMethods = _memberFinder.FindIndexer(expr.Type, args);
|
if (applicableMethods.Count == 0)
|
{
|
throw ParseException.Create(errorPos, ErrorMessages.NoApplicableIndexer,
|
TypeUtils.GetTypeName(expr.Type));
|
}
|
|
if (applicableMethods.Count > 1)
|
{
|
throw ParseException.Create(errorPos, ErrorMessages.AmbiguousIndexerInvocation,
|
TypeUtils.GetTypeName(expr.Type));
|
}
|
|
var indexer = (IndexerData)applicableMethods[0];
|
return Expression.Property(expr, indexer.Indexer, indexer.PromotedParameters);
|
}
|
|
private bool IsDynamicExpression(Expression instance)
|
{
|
return instance != null &&
|
(instance.NodeType == ExpressionType.Dynamic ||
|
(_arguments.Settings.LateBindObject && instance.Type == typeof(object)));
|
}
|
|
//static bool IsEnumType(Type type)
|
//{
|
// return GetNonNullableType(type).IsEnum;
|
//}
|
|
private void CheckAndPromoteOperand(MethodBase[] unarySignatures, ref Expression expr)
|
{
|
var args = PrepareOperandArguments(unarySignatures, new[] { expr });
|
|
expr = args[0];
|
}
|
|
private void CheckAndPromoteOperands(MethodBase[] binarySignatures, ref Expression left, ref Expression right)
|
{
|
// if one of the operands is the nullable version of the other, promote the non-nullablte to the nullable version
|
if (TypeUtils.TryGetNonNullableType(left.Type, out var nonNullableLeftType) && nonNullableLeftType == right.Type)
|
right = GenerateNullableTypeConversion(right);
|
else if (TypeUtils.TryGetNonNullableType(right.Type, out var nonNullableRightType) && nonNullableRightType == left.Type)
|
left = GenerateNullableTypeConversion(left);
|
|
var args = PrepareOperandArguments(binarySignatures, new[] { left, right });
|
|
left = args[0];
|
right = args[1];
|
}
|
|
private IList<Expression> PrepareOperandArguments(MethodBase[] signatures, Expression[] args)
|
{
|
var applicableMethods = MethodResolution.FindBestMethod(signatures, args);
|
if (applicableMethods.Count == 1)
|
return applicableMethods[0].PromotedParameters;
|
|
return args;
|
}
|
|
private static bool IsWritable(Expression expression)
|
{
|
switch (expression.NodeType)
|
{
|
case ExpressionType.Index:
|
var indexer = ((IndexExpression)expression).Indexer;
|
return indexer == null || indexer.CanWrite;
|
case ExpressionType.MemberAccess:
|
var member = ((MemberExpression)expression).Member;
|
var prop = member as PropertyInfo;
|
if (prop != null)
|
return prop.CanWrite;
|
else
|
{
|
var field = (FieldInfo)member;
|
return !(field.IsInitOnly || field.IsLiteral);
|
}
|
case ExpressionType.Parameter:
|
return true;
|
}
|
|
return false;
|
}
|
|
private static bool IsDynamicWritable(Expression expression, out Func<Expression, Expression> toWritableExpression)
|
{
|
toWritableExpression = null;
|
if (expression.NodeType != ExpressionType.Dynamic)
|
return false;
|
|
var dynamicExpression = (DynamicExpression)expression;
|
if (!(dynamicExpression.Binder is IConvertibleToWritableBinder convertibleBinder))
|
return false;
|
|
toWritableExpression = value => Expression.Dynamic(convertibleBinder.ToWritableBinder(), typeof(object), dynamicExpression.Arguments.Concat(new[] { value }));
|
return true;
|
}
|
|
private Expression GenerateEqual(Expression left, Expression right)
|
{
|
return GenerateBinary(ExpressionType.Equal, left, right);
|
}
|
|
private Expression GenerateNotEqual(Expression left, Expression right)
|
{
|
return GenerateBinary(ExpressionType.NotEqual, left, right);
|
}
|
|
private Expression GenerateGreaterThan(Expression left, Expression right)
|
{
|
if (left.Type == typeof(string))
|
{
|
return Expression.GreaterThan(
|
GenerateStaticMethodCall("Compare", left, right),
|
Expression.Constant(0)
|
);
|
}
|
return GenerateBinary(ExpressionType.GreaterThan, left, right);
|
}
|
|
private Expression GenerateGreaterThanEqual(Expression left, Expression right)
|
{
|
if (left.Type == typeof(string))
|
{
|
return Expression.GreaterThanOrEqual(
|
GenerateStaticMethodCall("Compare", left, right),
|
Expression.Constant(0)
|
);
|
}
|
return GenerateBinary(ExpressionType.GreaterThanOrEqual, left, right);
|
}
|
|
private Expression GenerateLessThan(Expression left, Expression right)
|
{
|
if (left.Type == typeof(string))
|
{
|
return Expression.LessThan(
|
GenerateStaticMethodCall("Compare", left, right),
|
Expression.Constant(0)
|
);
|
}
|
|
return GenerateBinary(ExpressionType.LessThan, left, right);
|
}
|
|
private Expression GenerateLessThanEqual(Expression left, Expression right)
|
{
|
if (left.Type == typeof(string))
|
{
|
return Expression.LessThanOrEqual(
|
GenerateStaticMethodCall("Compare", left, right),
|
Expression.Constant(0)
|
);
|
}
|
return GenerateBinary(ExpressionType.LessThanOrEqual, left, right);
|
}
|
|
private Expression GenerateBinary(ExpressionType binaryType, Expression left, Expression right)
|
{
|
if (IsDynamicExpression(left) || IsDynamicExpression(right))
|
return GenerateBinaryDynamic(binaryType, left, right);
|
|
// enum bitwise operations are not resolved properly by Linq
|
var bitwiseOps = new[] { ExpressionType.Or, ExpressionType.And };
|
if (left.Type.IsEnum && right.Type == left.Type && bitwiseOps.Contains(binaryType))
|
return GenerateBinaryEnums(binaryType, left, right);
|
|
// find the overloaded binary operator
|
string opName;
|
|
var liftToNull = true;
|
switch (binaryType)
|
{
|
case ExpressionType.OrElse: opName = "op_BitwiseOr"; break;
|
case ExpressionType.Or: opName = "op_BitwiseOr"; break;
|
case ExpressionType.ExclusiveOr: opName = "op_ExclusiveOr"; break;
|
case ExpressionType.AndAlso: opName = "op_BitwiseAnd"; break;
|
case ExpressionType.And: opName = "op_BitwiseAnd"; break;
|
case ExpressionType.Add: opName = "op_Addition"; break;
|
case ExpressionType.Subtract: opName = "op_Subtraction"; break;
|
case ExpressionType.Multiply: opName = "op_Multiply"; break;
|
case ExpressionType.Divide: opName = "op_Division"; break;
|
case ExpressionType.Modulo: opName = "op_Modulus"; break;
|
case ExpressionType.RightShift: opName = "op_RightShift"; break;
|
case ExpressionType.LeftShift: opName = "op_LeftShift"; break;
|
case ExpressionType.Equal: opName = "op_Equality"; liftToNull = false; break;
|
case ExpressionType.NotEqual: opName = "op_Inequality"; liftToNull = false; break;
|
case ExpressionType.GreaterThan: opName = "op_GreaterThan"; liftToNull = false; break;
|
case ExpressionType.GreaterThanOrEqual: opName = "op_GreaterThanOrEqual"; liftToNull = false; break;
|
case ExpressionType.LessThan: opName = "op_LessThan"; liftToNull = false; break;
|
case ExpressionType.LessThanOrEqual: opName = "op_LessThanOrEqual"; liftToNull = false; break;
|
default: opName = null; break;
|
}
|
|
var applicableMethod = FindBinaryOperator(opName, left, right);
|
|
MethodInfo operatorMethod = null;
|
if (applicableMethod != null)
|
{
|
operatorMethod = applicableMethod.MethodBase as MethodInfo;
|
left = applicableMethod.PromotedParameters[0];
|
right = applicableMethod.PromotedParameters[1];
|
}
|
|
if (binaryType == ExpressionType.OrElse)
|
{
|
UnaryExpression convert = Expression.Convert(left, typeof(bool));
|
|
ConstantExpression trueValue = Expression.Constant(true);
|
// 使用 Condition 实现短路
|
return Expression.Condition(convert, trueValue, Expression.Convert(right, typeof(bool)));
|
}
|
|
|
// if no operator was found, the default Linq resolution will occur
|
return Expression.MakeBinary(binaryType, left, right, liftToNull, operatorMethod);
|
}
|
|
public static bool ConvertBool(object obj)
|
{
|
try
|
{
|
var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static;
|
var type = obj.GetType();
|
var m = type.GetMethod("ToBool", flags);
|
if (m == null)
|
{
|
return (bool)obj;
|
}
|
return (bool)m.Invoke(null, new[] { obj });
|
}
|
catch (Exception ex)
|
{
|
return (bool)obj;
|
}
|
}
|
|
private static Expression ConvertBoolExpression(Expression expression)
|
{
|
var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static;
|
var m = typeof(Parser).GetMethod("ConvertBool", flags);
|
return Expression.Call(null, m, new[] { expression });
|
}
|
|
private Expression GenerateBinaryDynamic(ExpressionType binaryType, Expression left, Expression right)
|
{
|
//binary binder for dynamic type does not support AndAlso and OrElse as valid operations
|
if (binaryType == ExpressionType.AndAlso || binaryType == ExpressionType.OrElse)
|
{
|
if (IsDynamicExpression(left))
|
{
|
//left = Expression.Convert(left, typeof(bool));
|
left = ConvertBoolExpression(left);
|
}
|
|
if (IsDynamicExpression(right))
|
{
|
//right = Expression.Convert(right, typeof(bool));
|
right = ConvertBoolExpression(right);
|
}
|
|
return Expression.MakeBinary(binaryType, left, right);
|
}
|
|
var binder = Microsoft.CSharp.RuntimeBinder.Binder.BinaryOperation(
|
CSharpBinderFlags.None,
|
binaryType,
|
typeof(Parser),
|
new CSharpArgumentInfo[] {
|
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
|
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)}
|
);
|
|
return Expression.Dynamic(binder, typeof(object), left, right);
|
}
|
|
private Expression GenerateBinaryEnums(ExpressionType binaryType, Expression left, Expression right)
|
{
|
var enumType = left.Type;
|
var underlyingType = enumType.GetEnumUnderlyingType();
|
left = Expression.Convert(left, underlyingType);
|
right = Expression.Convert(right, underlyingType);
|
|
var op = Expression.MakeBinary(binaryType, left, right);
|
return Expression.Convert(op, enumType);
|
}
|
|
private MethodData FindBinaryOperator(string operatorName, Expression left, Expression right)
|
{
|
if (operatorName == null)
|
return null;
|
|
var errorPos = _token.pos;
|
var leftType = left.Type;
|
var rightType = right.Type;
|
// it's a lambda to avoid useless allocation
|
ParseException CreateAmbiguousOperatorException() => ParseException.Create(errorPos, ErrorMessages.AmbiguousBinaryOperatorInvocation, operatorName, TypeUtils.GetTypeName(leftType), TypeUtils.GetTypeName(rightType));
|
|
var args = new[] { left, right };
|
|
MethodData userDefinedOperator = null;
|
|
// try to find the user defined operator on both operands
|
var opOnLeftType = _memberFinder.FindMethods(leftType, operatorName, true, args);
|
if (opOnLeftType.Count > 1)
|
throw CreateAmbiguousOperatorException();
|
|
if (opOnLeftType.Count == 1)
|
userDefinedOperator = opOnLeftType[0];
|
|
if (leftType != rightType)
|
{
|
var opOnRightType = _memberFinder.FindMethods(rightType, operatorName, true, args);
|
if (opOnRightType.Count > 1)
|
throw CreateAmbiguousOperatorException();
|
|
MethodData rightOperator = null;
|
if (opOnRightType.Count == 1)
|
rightOperator = opOnRightType[0];
|
|
// we found a matching user defined operator on either type, but it might be the same method
|
if (userDefinedOperator != null && rightOperator != null && !ReferenceEquals(userDefinedOperator.MethodBase, rightOperator.MethodBase))
|
throw CreateAmbiguousOperatorException();
|
|
// we didn't find an operator on the left type, but we found one on the right type
|
if (userDefinedOperator == null && rightOperator != null)
|
userDefinedOperator = rightOperator;
|
}
|
|
return userDefinedOperator;
|
}
|
|
private static Expression GenerateStringConcat(Expression left, Expression right)
|
{
|
var leftObj = GenerateStringConcatOperand(left);
|
var rightObj = GenerateStringConcatOperand(right);
|
|
return Expression.Call(
|
null,
|
ReflectionExtensions.StringConcatMethod,
|
new[] { leftObj, rightObj });
|
}
|
|
private static Expression ToStringOrNull(Expression expression)
|
{
|
var nullableExpression = GenerateNullableTypeConversion(expression);
|
|
var expressionTypeNullConstant = Expression.Constant(null, nullableExpression.Type);
|
var stringNullConstant = Expression.Constant(null, typeof(string));
|
|
Expression condition = Expression.Equal(
|
nullableExpression,
|
expressionTypeNullConstant);
|
|
return Expression.Condition(
|
condition,
|
stringNullConstant,
|
Expression.Call(expression, ReflectionExtensions.ObjectToStringMethod));
|
}
|
|
private static Expression GenerateStringConcatOperand(Expression expression)
|
{
|
return expression.Type != typeof(string)
|
? ToStringOrNull(expression)
|
: expression;
|
}
|
|
private static Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right)
|
{
|
var staticMethod = left.Type.GetMethod(methodName, new[] { left.Type, right.Type });
|
return Expression.Call(null, staticMethod, new[] { left, right });
|
}
|
|
private void SetTextPos(int pos)
|
{
|
_parsePosition = pos;
|
_parseChar = _parsePosition < _expressionTextLength ? _expressionText[_parsePosition] : '\0';
|
}
|
|
private void NextChar()
|
{
|
if (_parsePosition < _expressionTextLength)
|
_parsePosition++;
|
|
_parseChar = _parsePosition < _expressionTextLength ? _expressionText[_parsePosition] : '\0';
|
}
|
|
private void PreviousChar()
|
{
|
SetTextPos(_parsePosition - 1);
|
}
|
|
private void NextToken()
|
{
|
while (char.IsWhiteSpace(_parseChar))
|
NextChar();
|
|
TokenId t;
|
var tokenPos = _parsePosition;
|
switch (_parseChar)
|
{
|
case '!':
|
NextChar();
|
if (_parseChar == '=')
|
{
|
NextChar();
|
t = TokenId.ExclamationEqual;
|
}
|
else
|
{
|
t = TokenId.Exclamation;
|
}
|
break;
|
case '%':
|
NextChar();
|
t = TokenId.Percent;
|
break;
|
case '&':
|
NextChar();
|
if (_parseChar == '&')
|
{
|
NextChar();
|
t = TokenId.DoubleAmphersand;
|
}
|
else
|
{
|
t = TokenId.Amphersand;
|
}
|
break;
|
case '(':
|
NextChar();
|
t = TokenId.OpenParen;
|
break;
|
case ')':
|
NextChar();
|
t = TokenId.CloseParen;
|
break;
|
case '*':
|
NextChar();
|
t = TokenId.Asterisk;
|
break;
|
case '+':
|
NextChar();
|
t = TokenId.Plus;
|
break;
|
case ',':
|
NextChar();
|
t = TokenId.Comma;
|
break;
|
case '-':
|
NextChar();
|
t = TokenId.Minus;
|
break;
|
case '~':
|
NextChar();
|
t = TokenId.Tilde;
|
break;
|
case '.':
|
NextChar();
|
|
if (char.IsDigit(_parseChar))
|
{
|
t = TokenId.RealLiteral;
|
do
|
{
|
NextChar();
|
} while (char.IsDigit(_parseChar));
|
if (_parseChar == 'E' || _parseChar == 'e')
|
{
|
t = TokenId.RealLiteral;
|
NextChar();
|
if (_parseChar == '+' || _parseChar == '-')
|
NextChar();
|
ValidateDigit();
|
do
|
{
|
NextChar();
|
} while (char.IsDigit(_parseChar));
|
}
|
if (_parseChar == 'F' || _parseChar == 'f' || _parseChar == 'M' || _parseChar == 'm')
|
NextChar();
|
break;
|
}
|
|
t = TokenId.Dot;
|
break;
|
case '/':
|
NextChar();
|
t = TokenId.Slash;
|
break;
|
case ':':
|
NextChar();
|
t = TokenId.Colon;
|
break;
|
case '<':
|
NextChar();
|
if (_parseChar == '=')
|
{
|
NextChar();
|
t = TokenId.LessThanEqual;
|
}
|
else
|
{
|
t = TokenId.LessThan;
|
}
|
break;
|
case '=':
|
NextChar();
|
if (_parseChar == '=')
|
{
|
NextChar();
|
t = TokenId.DoubleEqual;
|
}
|
else if (_parseChar == '>')
|
{
|
NextChar();
|
t = TokenId.LambdaArrow;
|
}
|
else
|
{
|
t = TokenId.Equal;
|
}
|
break;
|
case '>':
|
NextChar();
|
if (_parseChar == '=')
|
{
|
NextChar();
|
t = TokenId.GreaterThanEqual;
|
}
|
else
|
{
|
t = TokenId.GreaterThan;
|
}
|
break;
|
case '?':
|
NextChar();
|
if (_parseChar == '?')
|
{
|
NextChar();
|
t = TokenId.QuestionQuestion;
|
}
|
else
|
{
|
t = TokenId.Question;
|
}
|
break;
|
case '[':
|
NextChar();
|
t = TokenId.OpenBracket;
|
break;
|
case ']':
|
NextChar();
|
t = TokenId.CloseBracket;
|
break;
|
case '{':
|
NextChar();
|
t = TokenId.OpenCurlyBracket;
|
break;
|
case '}':
|
NextChar();
|
t = TokenId.CloseCurlyBracket;
|
break;
|
case '|':
|
NextChar();
|
if (_parseChar == '|')
|
{
|
NextChar();
|
t = TokenId.DoubleBar;
|
}
|
else
|
{
|
t = TokenId.Bar;
|
}
|
break;
|
case '"':
|
NextChar();
|
var isEscapeS = false;
|
var isEndS = _parseChar == '\"';
|
while (_parsePosition < _expressionTextLength && !isEndS)
|
{
|
isEscapeS = _parseChar == '\\' && !isEscapeS;
|
NextChar();
|
isEndS = (_parseChar == '\"' && !isEscapeS);
|
}
|
|
if (_parsePosition == _expressionTextLength)
|
throw ParseException.Create(_parsePosition, ErrorMessages.UnterminatedStringLiteral);
|
|
NextChar();
|
|
t = TokenId.StringLiteral;
|
break;
|
case '\'':
|
NextChar();
|
var isEscapeC = false;
|
var isEndC = false;
|
while (_parsePosition < _expressionTextLength && !isEndC)
|
{
|
isEscapeC = _parseChar == '\\' && !isEscapeC;
|
NextChar();
|
isEndC = (_parseChar == '\'' && !isEscapeC);
|
}
|
|
if (_parsePosition == _expressionTextLength)
|
throw ParseException.Create(_parsePosition, ErrorMessages.UnterminatedStringLiteral);
|
|
NextChar();
|
|
t = TokenId.CharLiteral;
|
break;
|
case '^':
|
NextChar();
|
t = TokenId.Caret;
|
break;
|
default:
|
|
if (char.IsLetter(_parseChar) || _parseChar == '@' || _parseChar == '_')
|
{
|
do
|
{
|
NextChar();
|
} while (char.IsLetterOrDigit(_parseChar) || _parseChar == '_');
|
t = TokenId.Identifier;
|
break;
|
}
|
|
if (char.IsDigit(_parseChar))
|
{
|
//RealLiteral if DefaultNumberType settings is set to real type
|
if (_defaultNumberType == DefaultNumberType.Single || _defaultNumberType == DefaultNumberType.Double || _defaultNumberType == DefaultNumberType.Decimal)
|
{
|
t = TokenId.RealLiteral;
|
}
|
else
|
{
|
//IntegerLiteral by default
|
t = TokenId.IntegerLiteral;
|
}
|
|
// binary and hexadecimal integer literals
|
var canBeRealLiteral = true;
|
if (_parseChar == '0')
|
{
|
NextChar();
|
if (_parseChar == 'x' || _parseChar == 'X')
|
{
|
canBeRealLiteral = false;
|
do
|
{
|
NextChar();
|
} while (char.IsDigit(_parseChar) || (_parseChar >= 'a' && _parseChar <= 'f') || (_parseChar >= 'A' && _parseChar <= 'F'));
|
|
PreviousChar();
|
}
|
else if (_parseChar == 'b' || _parseChar == 'B')
|
{
|
canBeRealLiteral = false;
|
do
|
{
|
NextChar();
|
} while (_parseChar == '0' || _parseChar == '1');
|
|
PreviousChar();
|
}
|
else
|
{
|
PreviousChar();
|
}
|
}
|
|
do
|
{
|
NextChar();
|
} while (char.IsDigit(_parseChar));
|
|
if (canBeRealLiteral && _parseChar == '.')
|
{
|
NextChar();
|
if (char.IsDigit(_parseChar))
|
{
|
t = TokenId.RealLiteral;
|
do
|
{
|
NextChar();
|
} while (char.IsDigit(_parseChar));
|
}
|
else
|
{
|
PreviousChar();
|
break;
|
}
|
}
|
|
if (canBeRealLiteral && (_parseChar == 'E' || _parseChar == 'e'))
|
{
|
t = TokenId.RealLiteral;
|
NextChar();
|
if (_parseChar == '+' || _parseChar == '-')
|
NextChar();
|
ValidateDigit();
|
do
|
{
|
NextChar();
|
} while (char.IsDigit(_parseChar));
|
}
|
|
if (canBeRealLiteral && (_parseChar == 'D' || _parseChar == 'd' || _parseChar == 'F' || _parseChar == 'f' || _parseChar == 'M' || _parseChar == 'm'))
|
{
|
t = TokenId.RealLiteral;
|
NextChar();
|
}
|
|
// 'U' | 'u' | 'L' | 'l' | 'UL' | 'Ul' | 'uL' | 'ul' | 'LU' | 'Lu' | 'lU' | 'lu'
|
if (_parseChar == 'U' || _parseChar == 'u')
|
{
|
t = TokenId.IntegerLiteral;
|
NextChar();
|
if (_parseChar == 'L' || _parseChar == 'l')
|
NextChar();
|
}
|
else if (_parseChar == 'L' || _parseChar == 'l')
|
{
|
t = TokenId.IntegerLiteral;
|
NextChar();
|
if (_parseChar == 'U' || _parseChar == 'u')
|
NextChar();
|
}
|
|
break;
|
}
|
if (_parsePosition == _expressionTextLength)
|
{
|
if (_token.id == TokenId.End)
|
throw new InvalidOperationException(ErrorMessages.NextTokenAtEnd);
|
|
t = TokenId.End;
|
break;
|
}
|
throw ParseException.Create(_parsePosition, ErrorMessages.InvalidCharacter, _parseChar);
|
}
|
_token.id = t;
|
_token.text = _expressionText.Substring(tokenPos, _parsePosition - tokenPos);
|
_token.pos = tokenPos;
|
}
|
|
private string GetIdentifier()
|
{
|
ValidateToken(TokenId.Identifier, ErrorMessages.IdentifierExpected);
|
var id = _token.text;
|
if (id.Length > 1 && id[0] == '@')
|
id = id.Substring(1);
|
return id;
|
}
|
|
private void ValidateDigit()
|
{
|
if (!char.IsDigit(_parseChar))
|
throw ParseException.Create(_parsePosition, ErrorMessages.DigitExpected);
|
}
|
|
// ReSharper disable once UnusedParameter.Local
|
private void ValidateToken(TokenId t, string errorMessage)
|
{
|
if (_token.id != t)
|
throw ParseException.Create(_token.pos, errorMessage);
|
}
|
|
// ReSharper disable once UnusedParameter.Local
|
private void ValidateToken(TokenId t)
|
{
|
if (_token.id != t)
|
throw ParseException.Create(_token.pos, ErrorMessages.SyntaxError);
|
}
|
|
private static Exception WrapWithParseException(int pos, string format, Exception ex, params object[] args)
|
{
|
return new ParseException(string.Format(format, args), pos, ex);
|
}
|
|
private static Expression GenerateNullableTypeConversion(Expression expr)
|
{
|
var exprType = expr.Type;
|
|
if (!exprType.IsValueType || TypeUtils.IsNullableType(exprType))
|
{
|
return expr;
|
}
|
|
var conversionType = TypeUtils.MakeNullable(exprType);
|
return Expression.Convert(expr, conversionType);
|
}
|
}
|
}
|