Files
radzen-blazor/Radzen.Blazor/ExpressionParser.cs
2026-06-15 18:44:02 +03:00

1155 lines
39 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Linq;
namespace Radzen;
#nullable enable
/// <summary>
/// Parse lambda expressions from strings.
/// </summary>
[RequiresUnreferencedCode(TrimMessages.ExpressionParserReflection)]
public class ExpressionParser
{
/// <summary>
/// Parses a lambda expression that returns a boolean value.
/// </summary>
public static Expression<Func<T, bool>> ParsePredicate<T>(string expression, Func<string, Type?>? typeResolver = null)
{
return ParseLambda<T, bool>(expression, typeResolver);
}
/// <summary>
/// Parses a lambda expression that returns a typed result.
/// </summary>
public static Expression<Func<T, TResult>> ParseLambda<T, TResult>(string expression, Func<string, Type?>? typeResolver = null)
{
var lambda = ParseLambda<T>(expression, typeResolver);
return Expression.Lambda<Func<T, TResult>>(lambda.Body, lambda.Parameters[0]);
}
/// <summary>
/// Parses a lambda expression that returns untyped result.
/// </summary>
public static LambdaExpression ParseLambda<T>(string expression, Func<string, Type?>? typeLocator = null)
{
return ParseLambda(expression, typeof(T), typeLocator);
}
/// <summary>
/// Parses a lambda expression that returns untyped result.
/// </summary>
public static LambdaExpression ParseLambda(string expression, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type, Func<string, Type?>? typeResolver = null)
{
var parser = new ExpressionParser(expression, typeResolver);
return parser.ParseLambda(type);
}
private readonly List<Token> tokens;
private int position;
private readonly Func<string, Type?>? typeResolver;
private readonly Stack<ParameterExpression> parameterStack = new();
private ExpressionParser(string expression, Func<string, Type?>? typeResolver = null)
{
this.typeResolver = typeResolver;
tokens = ExpressionLexer.Scan(expression);
}
Token Expect(TokenType type)
{
if (position >= tokens.Count)
{
throw new InvalidOperationException($"Unexpected end of expression. Expected token: {type}");
}
var token = tokens[position];
if (token.Type != type)
{
throw new InvalidOperationException($"Unexpected token: {token.Type}. Expected: {type}");
}
position++;
return token;
}
void Advance(int count)
{
position += count;
}
Token Peek(int offset = 0)
{
if (position + offset >= tokens.Count)
{
return new Token(TokenType.None, string.Empty);
}
return tokens[position + offset];
}
private LambdaExpression ParseLambda([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type paramType)
{
var parameterIdentifier = Expect(TokenType.Identifier);
var parameter = Expression.Parameter(paramType, parameterIdentifier.Value);
parameterStack.Push(parameter);
Expect(TokenType.EqualsGreaterThan);
var body = ParseExpression(parameter);
parameterStack.Pop();
return Expression.Lambda(body, parameter);
}
private Expression ParseExpression(ParameterExpression parameter)
{
var left = ParseBinary(parameter);
var token = Peek();
if (token.Type is TokenType.AmpersandAmpersand)
{
Advance(1);
var right = ParseExpression(parameter) ?? throw new InvalidOperationException($"Expected expression after {token.Value} at position {position}");
left = Expression.AndAlso(left, right);
}
else if (token.Type is TokenType.BarBar)
{
Advance(1);
var right = ParseExpression(parameter) ?? throw new InvalidOperationException($"Expected expression after {token.Value} at position {position}");
left = Expression.OrElse(left, right);
}
return left;
}
private Expression ParseBinary(ParameterExpression parameter)
{
var left = ParseNullCoalescing(parameter);
var token = Peek();
if (token.Type is TokenType.EqualsEquals or TokenType.NotEquals or TokenType.GreaterThan or TokenType.LessThan or TokenType.LessThanOrEqual or TokenType.GreaterThanOrEqual)
{
Advance(1);
var right = ParseBinary(parameter) ?? throw new InvalidOperationException($"Expected expression after {token.Value} at position {position}");
AlignBinaryOperandTypes(ref left, ref right);
left = Expression.MakeBinary(token.Type.ToExpressionType(), left, ConvertIfNeeded(right, left.Type));
}
return left;
}
private Expression ParseNullCoalescing(ParameterExpression parameter)
{
var left = ParseTernary(parameter);
var token = Peek();
while (token.Type == TokenType.QuestionMarkQuestionMark)
{
Advance(1);
var right = ParseTernary(parameter) ?? throw new InvalidOperationException($"Expected expression after ?? at position {position}");
if (right.Type == typeof(object))
{
right = ConvertIfNeeded(right, left.Type);
}
left = Expression.Coalesce(left, right);
token = Peek();
}
return left;
}
private Expression ParseTernary(ParameterExpression parameter)
{
var condition = ParseOr(parameter);
if (Peek().Type == TokenType.QuestionMark)
{
Advance(1);
var trueExpression = ParseOr(parameter);
Expect(TokenType.Colon);
var falseExpression = ParseOr(parameter);
if (trueExpression is ConstantExpression trueConst && trueConst.Value == null && falseExpression is not ConstantExpression)
{
trueExpression = Expression.Constant(null, falseExpression.Type);
}
else if (falseExpression is ConstantExpression falseConst && falseConst.Value == null && trueExpression is not ConstantExpression)
{
falseExpression = Expression.Constant(null, trueExpression.Type);
}
var ternary = Expression.Condition(condition, trueExpression, falseExpression);
return ParseMemberAccess(ternary, parameter);
}
return ParseMemberAccess(condition, parameter);
}
private Expression ParseMemberAccess(Expression expression, ParameterExpression parameter)
{
var token = Peek();
while (token.Type is TokenType.Dot or TokenType.QuestionDot or TokenType.OpenBracket)
{
if (token.Type == TokenType.Dot)
{
Advance(1);
token = Expect(TokenType.Identifier);
if (Peek().Type == TokenType.OpenParen)
{
expression = ParseInvocation(expression, token.Value, parameter);
}
else
{
expression = Expression.PropertyOrField(expression, token.Value);
}
}
else if (token.Type == TokenType.QuestionDot)
{
Advance(1);
token = Expect(TokenType.Identifier);
var check = Expression.Equal(expression, Expression.Constant(null));
if (Peek().Type == TokenType.OpenParen)
{
var call = ParseInvocation(expression, token.Value, parameter);
expression = Expression.Condition(check, Expression.Constant(null, call.Type), call);
}
else
{
var access = Expression.PropertyOrField(expression, token.Value);
expression = Expression.Condition(check, Expression.Default(access.Type), access);
var nextToken = Peek();
if (nextToken.Type == TokenType.Dot || nextToken.Type == TokenType.QuestionDot)
{
var nextAccess = ParseMemberAccess(access, parameter);
expression = Expression.Condition(check, Expression.Default(nextAccess.Type), nextAccess);
}
}
}
else if (token.Type == TokenType.OpenBracket)
{
Advance(1);
var index = ParseExpression(parameter);
Expect(TokenType.CloseBracket);
if (expression.Type.IsArray)
{
expression = Expression.ArrayIndex(expression, index);
}
else
{
var indexer = expression.Type.GetProperty("Item") ?? throw new InvalidOperationException($"Type {expression.Type} does not have an indexer property");
expression = Expression.Property(expression, indexer, index);
}
}
token = Peek();
}
return expression;
}
private MethodCallExpression ParseInvocation(Expression expression, string methodName, ParameterExpression parameter)
{
Advance(1);
var arguments = new List<Expression>();
if (Peek().Type != TokenType.CloseParen)
{
while (Peek().Type != TokenType.CloseParen)
{
var token = Peek();
if (token.Type == TokenType.Identifier && Peek(1).Type == TokenType.EqualsGreaterThan)
{
var lambdaParameterName = token.Value;
Advance(2);
Type? lambdaParameterType = null;
var extensionMethod = typeof(Enumerable).GetMethods(BindingFlags.Public | BindingFlags.Static)
.FirstOrDefault(m => m.Name == methodName && m.GetParameters().Length == 2);
if (extensionMethod != null)
{
lambdaParameterType = GetItemType(expression.Type);
}
if (lambdaParameterType == null)
{
throw new InvalidOperationException($"Could not infer type for lambda parameter {lambdaParameterName}");
}
var lambdaParameter = Expression.Parameter(lambdaParameterType, lambdaParameterName);
parameterStack.Push(lambdaParameter);
var lambdaBody = ParseExpression(lambdaParameter);
parameterStack.Pop();
arguments.Add(Expression.Lambda(lambdaBody, lambdaParameter));
}
else
{
arguments.Add(ParseExpression(parameter));
}
if (Peek().Type == TokenType.Comma)
{
Advance(1);
}
}
}
Expect(TokenType.CloseParen);
var argumentTypes = arguments.Select(a => a.Type).ToArray();
var method = expression.Type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance, null, argumentTypes, null);
if (method != null)
{
return Expression.Call(expression, method, arguments);
}
method = typeof(Enumerable).GetMethods(BindingFlags.Public | BindingFlags.Static)
.FirstOrDefault(m => m.Name == methodName && m.GetParameters().Length == arguments.Count + 1);
if (method != null)
{
var argumentType = GetItemType(expression.Type);
if (argumentType == null)
{
throw new InvalidOperationException($"Cannot determine item type for {expression.Type}");
}
if (method.IsGenericMethodDefinition)
{
method = method.MakeGenericMethod(argumentType);
}
var parameters = method.GetParameters();
var argumentsWithInstance = new[] { expression }.Concat(arguments).ToArray();
return Expression.Call(method, argumentsWithInstance.Select((a, index) => ConvertIfNeeded(a, parameters[index].ParameterType)));
}
throw new InvalidOperationException($"No suitable method '{methodName}' found for type '{expression.Type}'");
}
private static Type? GetItemType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type enumerableOrArray)
{
return enumerableOrArray.IsArray ? enumerableOrArray.GetElementType() : enumerableOrArray.GetGenericArguments()[0];
}
private Expression? ParseTerm(ParameterExpression parameter)
{
var token = Peek();
if (token.Type == TokenType.None)
{
return null;
}
if (token.Type == TokenType.OpenParen)
{
Advance(1);
if (TryParseCastExpression(parameter, out var expression))
{
return expression;
}
expression = ParseExpression(parameter);
Expect(TokenType.CloseParen);
return expression;
}
if (token.Type == TokenType.Identifier)
{
if (token.Value == "default" && Peek(1).Type == TokenType.OpenParen)
{
Advance(2);
if (!TryParseTypeReference(out var defaultType))
{
throw new InvalidOperationException($"Could not resolve type in default expression at position {position}");
}
Expect(TokenType.CloseParen);
return Expression.Default(defaultType);
}
var matchingParameter = parameterStack.FirstOrDefault(p => p.Name == token.Value);
if (matchingParameter != null)
{
Advance(1);
return ParseMemberAccess(matchingParameter, parameter);
}
var type = GetWellKnownType(token.Value);
if (type != null)
{
Advance(1);
return ParseStaticMemberAccess(type, parameter);
}
if (TryParseQualifiedType(out var qualifiedType))
{
return ParseStaticMemberAccess(qualifiedType, parameter);
}
if (Peek(1).Type == TokenType.OpenParen)
{
Advance(1);
return ParseInvocation(parameter, token.Value, parameter);
}
throw new InvalidOperationException($"Unexpected identifier: {token.Value}");
}
if (token.Type == TokenType.ExclamationMark)
{
Advance(1);
var operand = ParseTerm(parameter) ?? throw new InvalidOperationException($"Expected expression after ! at position {position}");
operand = ConvertIfNeeded(operand, typeof(bool));
return Expression.Not(operand);
}
if (token.Type == TokenType.Minus)
{
Advance(1);
var operand = ParseTerm(parameter) ?? throw new InvalidOperationException($"Expected expression after - at position {position}");
return Expression.Negate(operand);
}
if (token.Type == TokenType.Plus)
{
Advance(1);
var operand = ParseTerm(parameter) ?? throw new InvalidOperationException($"Expected expression after + at position {position}");
return operand;
}
switch (token.Type)
{
case TokenType.CharacterLiteral:
case TokenType.StringLiteral:
case TokenType.NullLiteral:
case TokenType.NumericLiteral:
case TokenType.TrueLiteral:
case TokenType.FalseLiteral:
Advance(1);
return token.ToConstantExpression();
case TokenType.New:
Advance(1);
token = Peek();
if (token.Type == TokenType.OpenBrace)
{
Advance(1);
var properties = new List<(string Name, Expression Expression)>();
if (Peek().Type != TokenType.CloseBrace)
{
do
{
token = Peek();
string propertyName;
Expression propertyExpression;
if (token.Type == TokenType.Identifier)
{
propertyName = token.Value;
Advance(1);
if (Peek().Type == TokenType.Dot || Peek().Type == TokenType.QuestionDot)
{
// Handle nested property access
Expression expr = propertyName == parameter.Name ? (Expression)parameter : Expression.Property(parameter, propertyName);
propertyExpression = ParseMemberAccess(expr, parameter);
// Get the last identifier token's value
var lastToken = tokens[position - 1];
if (lastToken.Type == TokenType.Identifier)
{
propertyName = lastToken.Value;
}
}
else
{
Expect(TokenType.Equals);
propertyExpression = ParseExpression(parameter);
}
}
else
{
propertyExpression = ParseExpression(parameter);
if (propertyExpression is MemberExpression memberExpression)
{
propertyName = memberExpression.Member.Name;
}
else
{
throw new InvalidOperationException($"Invalid anonymous type member expression at position {position}");
}
}
properties.Add((propertyName, propertyExpression));
if (Peek().Type == TokenType.Comma)
{
Advance(1);
}
else
{
break;
}
} while (Peek().Type != TokenType.CloseBrace);
}
Expect(TokenType.CloseBrace);
var propertyTypes = properties.Select(p => p.Expression.Type).ToArray();
var propertyNames = properties.Select(p => p.Name).ToArray();
var dynamicType = DynamicTypeFactory.CreateType(parameter.Type.Name, propertyNames, propertyTypes);
var bindings = properties.Select(p => Expression.Bind(dynamicType.GetProperty(p.Name)!, p.Expression));
return Expression.MemberInit(Expression.New(dynamicType), bindings);
}
else
{
Type? elementType = null;
if (TryParseQualifiedArrayType(out var parsedElementType))
{
elementType = parsedElementType;
}
Expect(TokenType.OpenBracket);
Expect(TokenType.CloseBracket);
Expect(TokenType.OpenBrace);
var elements = new List<Expression>();
if (Peek().Type != TokenType.CloseBrace)
{
do
{
elements.Add(ParseExpression(parameter));
if (Peek().Type == TokenType.Comma)
{
Advance(1);
}
else
{
break;
}
} while (Peek().Type != TokenType.CloseBrace);
}
Expect(TokenType.CloseBrace);
if (elementType == null)
{
elementType = elements.Count > 0 ? elements[0].Type : typeof(object);
}
return Expression.NewArrayInit(elementType, elements.Select(e => ConvertIfNeeded(e, elementType)));
}
default:
throw new InvalidOperationException($"Unexpected token: {token.Type} at position {position}");
}
}
private bool TryParseCastExpression(ParameterExpression parameter, out Expression expression)
{
expression = null!;
var token = Peek();
if (token.Type != TokenType.Identifier)
{
return false;
}
var typeName = new StringBuilder(token.Value);
var index = position + 1;
var typeCast = true;
var nullable = false;
while (index < tokens.Count)
{
token = tokens[index];
if (token.Type == TokenType.Dot)
{
index++;
if (index >= tokens.Count || tokens[index].Type != TokenType.Identifier)
{
typeCast = false;
break;
}
typeName.Append('.').Append(tokens[index].Value);
index++;
}
else if (token.Type == TokenType.QuestionMark)
{
nullable = true;
index++;
if (index >= tokens.Count || tokens[index].Type != TokenType.CloseParen)
{
typeCast = false;
break;
}
}
else if (token.Type == TokenType.CloseParen)
{
break;
}
else
{
typeCast = false;
break;
}
}
if (typeCast && index < tokens.Count && tokens[index].Type == TokenType.CloseParen)
{
var name = typeName.ToString();
var type = ResolveType(name) ?? throw new InvalidOperationException($"Could not resolve type: {typeName}");
if (nullable && type.IsValueType)
{
type = typeof(Nullable<>).MakeGenericType(type);
}
position = index;
Advance(1);
if (Peek().Type == TokenType.OpenParen && TryParseCastExpression(parameter, out var innerExpression))
{
expression = Expression.Convert(innerExpression, type);
}
else
{
var source = ParseTerm(parameter) ?? throw new InvalidOperationException($"Expected expression to cast at position {position}");
expression = Expression.Convert(source, type);
}
return true;
}
return false;
}
private Expression ParseStaticMemberAccess([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicMethods)] Type type, ParameterExpression parameter)
{
Expect(TokenType.Dot);
var token = Expect(TokenType.Identifier);
if (Peek().Type == TokenType.OpenParen)
{
return ParseStaticInvocation(type, token.Value, parameter);
}
else
{
var member = (MemberInfo?)type.GetProperty(token.Value) ?? type.GetField(token.Value);
if (member == null)
{
throw new InvalidOperationException($"Member {token.Value} not found on type {type.Name}");
}
return Expression.MakeMemberAccess(null, member);
}
throw new InvalidOperationException($"Expected method invocation after {token.Value} at position {position}");
}
private Expression ParseStaticInvocation([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type, string methodName, ParameterExpression parameter)
{
Advance(1);
var arguments = new List<Expression>();
if (Peek().Type != TokenType.CloseParen)
{
arguments.Add(ParseExpression(parameter));
while (Peek().Type == TokenType.Comma)
{
Advance(1);
arguments.Add(ParseExpression(parameter));
}
}
Expect(TokenType.CloseParen);
var method = type.GetMethod(methodName, [.. arguments.Select(a => a.Type)]) ?? throw new InvalidOperationException($"Method {methodName} not found on type {type.Name}");
return Expression.Call(null, method, arguments);
}
/// <summary>
/// Tries to parse a type reference (e.g. bool, int, System.DateTime, System.DateTime?) and returns the resolved type.
/// Advances position past the type name. Does not backtrack — expects the full token sequence to be a type.
/// Used for contexts like default(T) where the entire content is a type name.
/// </summary>
private bool TryParseTypeReference(out Type type)
{
type = null!;
var token = Peek();
if (token.Type != TokenType.Identifier)
{
return false;
}
var startPosition = position;
var parts = new List<string> { token.Value };
Advance(1);
while (Peek().Type == TokenType.Dot)
{
Advance(1);
token = Peek();
if (token.Type != TokenType.Identifier)
{
position = startPosition;
return false;
}
parts.Add(token.Value);
Advance(1);
}
var nullable = false;
if (Peek().Type == TokenType.QuestionMark)
{
nullable = true;
Advance(1);
}
var typeName = string.Join(".", parts);
var resolvedType = ResolveType(typeName);
if (resolvedType != null)
{
if (nullable && resolvedType.IsValueType)
{
resolvedType = typeof(Nullable<>).MakeGenericType(resolvedType);
}
type = resolvedType;
return true;
}
position = startPosition;
return false;
}
/// <summary>
/// Tries to parse a qualified type name (e.g. System.DateTime or System.DateTime?) and returns the resolved type.
/// Advances position past the type name. The next token will be . for member access or [ for array.
/// Uses backtracking to find the longest resolvable type prefix (e.g. System.DateTime in System.DateTime.SpecifyKind).
/// </summary>
private bool TryParseQualifiedType(out Type type)
{
type = null!;
var token = Peek();
if (token.Type != TokenType.Identifier)
{
return false;
}
var startPosition = position;
var parts = new List<string> { token.Value };
Advance(1);
while (Peek().Type == TokenType.Dot)
{
Advance(1);
token = Peek();
if (token.Type != TokenType.Identifier)
{
return false;
}
parts.Add(token.Value);
Advance(1);
}
var nullable = false;
if (Peek().Type == TokenType.QuestionMark)
{
nullable = true;
Advance(1);
}
for (var i = parts.Count; i >= 1; i--)
{
var typeName = string.Join(".", parts.Take(i));
var resolvedType = ResolveType(typeName);
if (resolvedType != null)
{
if (nullable && resolvedType.IsValueType)
{
resolvedType = typeof(Nullable<>).MakeGenericType(resolvedType);
}
position = startPosition;
var tokensToConsume = (i * 2) - 1 + (nullable ? 1 : 0);
for (var t = 0; t < tokensToConsume; t++)
{
Advance(1);
}
type = resolvedType;
return true;
}
}
position = startPosition;
return false;
}
/// <summary>
/// Tries to parse a qualified array element type (e.g. System.DateTime? or DateTime) before [].
/// Advances position past the type name to the [. Returns the element type for the array.
/// </summary>
private bool TryParseQualifiedArrayType(out Type? elementType)
{
elementType = null;
var token = Peek();
if (token.Type != TokenType.Identifier)
{
return false;
}
var startPosition = position;
var parts = new List<string> { token.Value };
Advance(1);
while (Peek().Type == TokenType.Dot)
{
Advance(1);
token = Peek();
if (token.Type != TokenType.Identifier)
{
position = startPosition;
return false;
}
parts.Add(token.Value);
Advance(1);
}
var nullable = false;
if (Peek().Type == TokenType.QuestionMark)
{
nullable = true;
Advance(1);
}
if (Peek().Type != TokenType.OpenBracket)
{
position = startPosition;
return false;
}
for (var i = parts.Count; i >= 1; i--)
{
var typeName = string.Join(".", parts.Take(i));
var resolvedType = ResolveType(typeName);
if (resolvedType != null)
{
if (nullable && resolvedType.IsValueType)
{
resolvedType = typeof(Nullable<>).MakeGenericType(resolvedType);
}
elementType = resolvedType;
return true;
}
}
position = startPosition;
return false;
}
/// <summary>
/// Resolves a type name using well-known types, the optional type resolver, or by searching loaded assemblies.
/// This allows any type from loaded assemblies to be resolved without hardcoding.
/// </summary>
private Type? ResolveType(string typeName)
{
return GetWellKnownType(typeName)
?? typeResolver?.Invoke(typeName)
?? ResolveTypeFromAssemblies(typeName);
}
[RequiresUnreferencedCode(TrimMessages.AssemblyScanning)]
private static Type? ResolveTypeFromAssemblies(string typeName)
{
return AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.FirstOrDefault(t =>
{
var fullName = t.FullName;
return fullName != null && fullName.Replace("+", ".", StringComparison.Ordinal) == typeName;
});
}
private static Type? GetWellKnownType(string typeName)
{
return typeName switch
{
nameof(DateTime) => typeof(DateTime),
nameof(DateOnly) => typeof(DateOnly),
nameof(TimeOnly) => typeof(TimeOnly),
nameof(DateTimeOffset) => typeof(DateTimeOffset),
nameof(Guid) => typeof(Guid),
nameof(CultureInfo) => typeof(CultureInfo),
nameof(DateTimeStyles) => typeof(DateTimeStyles),
nameof(DateTimeKind) => typeof(DateTimeKind),
nameof(Double) or "double" => typeof(double),
nameof(Single) or "float" => typeof(float),
nameof(Int32) or "int" => typeof(int),
nameof(Int64) or "long" => typeof(long),
nameof(Int16) or "short" => typeof(short),
nameof(Byte) or "byte" => typeof(byte),
nameof(SByte) or "sbyte" => typeof(sbyte),
nameof(UInt32) or "uint" => typeof(uint),
nameof(UInt64) or "ulong" => typeof(ulong),
nameof(UInt16) or "ushort" => typeof(ushort),
nameof(Boolean) or "bool" => typeof(bool),
nameof(Char) or "char" => typeof(char),
nameof(Decimal) or "decimal" => typeof(decimal),
nameof(String) or "string" => typeof(string),
nameof(Math) => typeof(Math),
nameof(Convert) => typeof(Convert),
_ => null
};
}
private Expression ParseOr(ParameterExpression parameter)
{
var left = ParseMemberAccess(ParseAnd(parameter), parameter);
var token = Peek();
while (token.Type == TokenType.BarBar)
{
Advance(1);
var right = ParseMemberAccess(ParseAnd(parameter) ?? throw new InvalidOperationException($"Expected expression after || at position {position}"), parameter);
left = Expression.OrElse(left, right);
token = Peek();
}
return left;
}
private Expression ParseAnd(ParameterExpression parameter)
{
var left = ParseMemberAccess(ParseComparison(parameter), parameter);
var token = Peek();
while (token.Type == TokenType.AmpersandAmpersand)
{
Advance(1);
var right = ParseMemberAccess(ParseComparison(parameter) ?? throw new InvalidOperationException($"Expected expression after && at position {position}"), parameter);
left = Expression.AndAlso(left, right);
token = Peek();
}
return left;
}
private Expression ParseComparison(ParameterExpression parameter)
{
var left = ParseShift(parameter);
var token = Peek();
if (token.Type is TokenType.EqualsEquals or TokenType.NotEquals or TokenType.GreaterThan or TokenType.LessThan or TokenType.LessThanOrEqual or TokenType.GreaterThanOrEqual)
{
Advance(1);
var right = ParseShift(parameter) ?? throw new InvalidOperationException($"Expected expression after {token.Value} at position {position}");
AlignBinaryOperandTypes(ref left, ref right);
left = Expression.MakeBinary(token.Type.ToExpressionType(), left, ConvertIfNeeded(right, left.Type));
}
return ParseBinaryAnd(left, parameter);
}
private Expression ParseBinaryAnd(Expression left, ParameterExpression parameter)
{
var token = Peek();
while (token.Type == TokenType.Ampersand)
{
Advance(1);
var right = ParseShift(parameter) ?? throw new InvalidOperationException($"Expected expression after & at position {position}");
left = Expression.MakeBinary(ExpressionType.And, left, ConvertIfNeeded(right, left.Type));
token = Peek();
}
return ParseBinaryXor(left, parameter);
}
private Expression ParseBinaryXor(Expression left, ParameterExpression parameter)
{
var token = Peek();
while (token.Type == TokenType.Caret)
{
Advance(1);
var right = ParseBinaryAnd(ParseShift(parameter), parameter) ?? throw new InvalidOperationException($"Expected expression after ^ at position {position}");
left = Expression.MakeBinary(ExpressionType.ExclusiveOr, left, ConvertIfNeeded(right, left.Type));
token = Peek();
}
return ParseBinaryOr(left, parameter);
}
private Expression ParseBinaryOr(Expression left, ParameterExpression parameter)
{
var token = Peek();
while (token.Type == TokenType.Bar)
{
Advance(1);
var right = ParseBinaryXor(ParseShift(parameter), parameter) ?? throw new InvalidOperationException($"Expected expression after | at position {position}");
left = Expression.MakeBinary(ExpressionType.Or, left, ConvertIfNeeded(right, left.Type));
token = Peek();
}
return left;
}
private Expression ParseShift(ParameterExpression parameter)
{
var left = ParseAdditive(parameter);
var token = Peek();
while (token.Type is TokenType.LessThanLessThan or TokenType.GreaterThanGreaterThan)
{
Advance(1);
var right = ParseAdditive(parameter) ?? throw new InvalidOperationException($"Expected expression after {token.Value} at position {position}");
left = Expression.MakeBinary(token.Type.ToExpressionType(), left, ConvertIfNeeded(right, left.Type));
token = Peek();
}
return left;
}
private Expression ParseAdditive(ParameterExpression parameter)
{
var left = ParseMultiplicative(parameter);
var token = Peek();
while (token.Type is TokenType.Plus or TokenType.Minus)
{
Advance(1);
var right = ParseMultiplicative(parameter) ?? throw new InvalidOperationException($"Expected expression after {token.Value} at position {position}");
if (token.Type == TokenType.Plus && left.Type == typeof(string))
{
left = Expression.Call(null, typeof(string).GetMethod(nameof(string.Concat), [typeof(string), typeof(string)])!, left, ConvertIfNeeded(right, typeof(string)));
}
else
{
left = Expression.MakeBinary(token.Type.ToExpressionType(), left, ConvertIfNeeded(right, left.Type));
}
token = Peek();
}
return left;
}
private Expression ParseMultiplicative(ParameterExpression parameter)
{
var left = ParseTerm(parameter) ?? throw new InvalidOperationException($"Expected expression at position {position}");
var token = Peek();
while (token.Type is TokenType.Star or TokenType.Slash)
{
Advance(1);
var right = ParseTerm(parameter) ?? throw new InvalidOperationException($"Expected expression after {token.Value} at position {position}");
left = Expression.MakeBinary(token.Type.ToExpressionType(), left, ConvertIfNeeded(right, left.Type));
token = Peek();
}
return left;
}
private static Expression ConvertIfNeeded(Expression expression, Type targetType)
{
if (expression is not LambdaExpression)
{
return expression.Type == targetType ? expression : Expression.Convert(expression, targetType);
}
return expression;
}
private static void AlignBinaryOperandTypes(ref Expression left, ref Expression right)
{
if (left.Type == right.Type)
{
return;
}
var leftUnderlying = Nullable.GetUnderlyingType(left.Type);
var rightUnderlying = Nullable.GetUnderlyingType(right.Type);
if (leftUnderlying != null && rightUnderlying == null && leftUnderlying == right.Type)
{
right = Expression.Convert(right, left.Type);
}
else if (rightUnderlying != null && leftUnderlying == null && rightUnderlying == left.Type)
{
left = Expression.Convert(left, right.Type);
}
}
}