mirror of
https://github.com/radzenhq/radzen-blazor.git
synced 2026-02-04 05:35:44 +00:00
919 lines
32 KiB
C#
919 lines
32 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
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>
|
|
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, 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(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}");
|
|
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(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)
|
|
{
|
|
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 (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;
|
|
var nullable = false;
|
|
|
|
if (token.Type == TokenType.Identifier)
|
|
{
|
|
var typeName = token.Value;
|
|
elementType = GetWellKnownType(typeName);
|
|
Advance(1);
|
|
|
|
if (Peek().Type == TokenType.QuestionMark)
|
|
{
|
|
nullable = true;
|
|
Advance(1);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
if (nullable)
|
|
{
|
|
elementType = typeof(Nullable<>).MakeGenericType(elementType);
|
|
}
|
|
|
|
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 = GetWellKnownType(name) ?? typeResolver?.Invoke(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(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(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);
|
|
}
|
|
|
|
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}");
|
|
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;
|
|
}
|
|
} |