Skip to content

Evaluator

El Evaluator es un servicio interno que evalúa un Árbol de Sintaxis Abstracta (AST) y construye expresiones lambda para representar la evaluación. Estas expresiones se pueden usar para filtrar o procesar datos dinámicamente de manera eficiente.

¿Cómo Funciona?

El Evaluator toma como entrada un nodo AST y genera una expresión lambda que se puede utilizar para evaluar condiciones complejas o aplicar filtros sobre colecciones de datos. Utiliza expresiones de LINQ y árboles de expresiones para realizar este procesamiento.

Métodos


El Evaluator incluye métodos clave para interpretar los nodos del AST y convertirlos en expresiones funcionales.

Evaluate

Type: public static Expression<Func<T, bool>> Evaluate<T>(AstNode node)

Este método principal toma un nodo raíz del Abstract Syntax Tree (AST) como entrada y genera una expresión lambda del tipo Func<T, bool>. Esta expresión representa la lógica de filtrado definida en el AST, y puede ser aplicada a una colección de objetos del tipo T. El proceso implica la conversión del AST en código ejecutable.

BuildExpression

Type: private static Expression BuildExpression(AstNode node, ParameterExpression parameter)

Este método, de uso interno del Evaluator, construye una expresión recursivamente basándose en el tipo del nodo del AST que se le proporciona. Es el encargado de analizar el tipo del nodo actual (ya sea una expresión, una condición o un operador) y delega la generación de la expresión a métodos más específicos. Este método permite navegar a través del árbol del AST de manera recursiva, garantizando que cada parte del árbol se transforme correctamente en una expresión.

BuildConditionExpression

Type: private static Expression BuildConditionExpression(AstNode node, ParameterExpression parameter)

Este método, de uso interno del Evaluator, tiene como propósito específico convertir un nodo del AST de tipo Condition en una expresión. Esta expresión realiza una comparación entre una propiedad de un objeto y un valor constante. El proceso implica la división de la condición en sus componentes básicos: la propiedad, el operador de comparación y el valor, y posteriormente, la construcción de la expresión de comparación correspondiente.

BuildLogicalExpression

Type: private static BinaryExpression BuildLogicalExpression(AstNode node, ParameterExpression parameter)

Este método, de uso interno del Evaluator, se encarga de crear expresiones lógicas, ya sean de tipo AND (Y) o OR (O), basándose en nodos de tipo Operator dentro del AST. Combina las expresiones construidas de los nodos hijos del operador, utilizando el operador lógico definido en el nodo del árbol. Esto permite construir condiciones complejas con múltiples niveles.

SortBy

Type: public static Expression<Func<T, object>> SortBy<T>(string propertyName)

Este método genera una expresión lambda del tipo Func<T, object>, que se puede utilizar para ordenar una colección de objetos del tipo T por una propiedad específica. Recibe el nombre de la propiedad como entrada y genera una expresión lambda que puede ser utilizada por LINQ u otras bibliotecas para realizar la operación de ordenamiento.

Implementación


El Evaluator se implementa en el espacio de nombres CodeDesignPlus.Net.Criteria. Utiliza expresiones lambda y árboles de expresiones para evaluar condiciones y construir filtros dinámicos.

using NodaTime;
using NodaTime.Text;
namespace CodeDesignPlus.Net.Criteria;
/// <summary>
/// Evaluates an abstract syntax tree (AST) and builds an expression to represent the evaluation.
/// </summary>
internal static class Evaluator
{
private static readonly InstantPattern pattern = InstantPattern.CreateWithInvariantCulture("yyyy-MM-ddTHH:mm:ss'Z'");
private static readonly string[] Operators = ["~=", "^=", "$=", "<=", ">=", "=", "<", ">"];
/// <summary>
/// Evaluates an ASTNode and builds an expression to represent the evaluation.
/// </summary>
/// <typeparam name="T">The type of the parameter in the expression.</typeparam>
/// <param name="node">The ASTNode to evaluate.</param>
/// <returns>An expression representing the evaluation of the ASTNode.</returns>
public static Expression<Func<T, bool>> Evaluate<T>(AstNode node)
{
var parameter = Expression.Parameter(typeof(T));
var expression = BuildExpression(node, parameter);
return Expression.Lambda<Func<T, bool>>(expression, parameter);
}
/// <summary>
/// Builds an expression based on the given AST node and parameter expression.
/// </summary>
/// <param name="node">The AST node to build the expression from.</param>
/// <param name="parameter">The parameter expression to use in the expression.</param>
/// <returns>The built expression.</returns>
private static Expression BuildExpression(AstNode node, ParameterExpression parameter)
{
return node.Type switch
{
AstType.Expression => BuildExpression(node.Children[0], parameter),
AstType.Condition => BuildConditionExpression(node, parameter),
AstType.Operator => BuildLogicalExpression(node, parameter),
_ => throw new CriteriaException($"Invalid AST node type: {node.Type}"),
};
}
/// <summary>
/// Builds a condition expression based on the given AST node and parameter expression.
/// </summary>
/// <param name="node">The AST node to build the condition expression from.</param>
/// <param name="parameter">The parameter expression to use in the expression.</param>
/// <returns>The built condition expression.</returns>
private static Expression BuildConditionExpression(AstNode node, ParameterExpression parameter)
{
var (propertyPath, operatorSymbol, value) = SplitCondition(node.Value);
var property = BuildPropertyExpression(propertyPath, parameter);
var constant = CreateConstantExpression(value, property.Type);
return BuildComparisonExpression(property, constant, operatorSymbol);
}
/// <summary>
/// Builds a logical expression based on the given AST node and parameter expression.
/// </summary>
/// <param name="node">The AST node to build the logical expression from.</param>
/// <param name="parameter">The parameter expression to use in the expression.</param>
/// <returns>The built logical expression.</returns>
private static BinaryExpression BuildLogicalExpression(AstNode node, ParameterExpression parameter)
{
var left = BuildExpression(node.Children[0], parameter);
var right = BuildExpression(node.Children[1], parameter);
return node.Value.ToLower() switch
{
"and" => Expression.AndAlso(left, right),
"or" => Expression.OrElse(left, right),
_ => throw new CriteriaException($"Invalid logical operator: {node.Value}")
};
}
/// <summary>
/// Splits a condition into property path, operator symbol, and value.
/// </summary>
/// <param name="condition">The condition to split.</param>
/// <returns>A tuple containing the property path, operator symbol, and value.</returns>
private static (string, string, string) SplitCondition(string condition)
{
foreach (var op in Operators)
{
var parts = condition.Split(new[] { op }, 2, StringSplitOptions.None);
if (parts.Length == 2)
return (parts[0], op, parts[1]);
}
throw new CriteriaException("Invalid condition format");
}
/// <summary>
/// Builds a property expression based on the given property path and parameter expression.
/// </summary>
/// <param name="propertyPath">The property path to build the expression from.</param>
/// <param name="parameter">The parameter expression to use in the expression.</param>
/// <returns>The built property expression.</returns>
private static Expression BuildPropertyExpression(string propertyPath, ParameterExpression parameter)
{
Expression propertyExpression = parameter;
foreach (var propertyName in propertyPath.Split('.'))
{
propertyExpression = Expression.Property(propertyExpression, propertyName);
}
return propertyExpression;
}
/// <summary>
/// Creates a constant expression based on the given value and target type.
/// </summary>
/// <param name="value">The value to create the constant expression from.</param>
/// <param name="targetType">The target type of the constant expression.</param>
/// <returns>The created constant expression.</returns>
private static ConstantExpression CreateConstantExpression(string value, Type targetType)
{
try
{
if (targetType == typeof(Instant))
{
var parseResult = pattern.Parse(value);
return Expression.Constant(parseResult.Value, targetType);
}
var convertedValue = Convert.ChangeType(value, targetType);
return Expression.Constant(convertedValue, targetType);
}
catch (Exception ex) when (ex is InvalidCastException || ex is FormatException)
{
throw new CriteriaException($"Invalid value '{value}' for type {targetType}", ex);
}
}
/// <summary>
/// Builds a comparison expression based on the given property, constant, and operator symbol.
/// </summary>
/// <param name="property">The property expression to use in the comparison.</param>
/// <param name="constant">The constant expression to use in the comparison.</param>
/// <param name="operatorSymbol">The operator symbol to use in the comparison.</param>
/// <returns>The built comparison expression.</returns>
private static Expression BuildComparisonExpression(Expression property, Expression constant, string operatorSymbol)
{
return operatorSymbol switch
{
"^=" => Expression.Call(property, typeof(string).GetMethod(nameof(string.StartsWith), new[] { typeof(string) })!, constant),
"$=" => Expression.Call(property, typeof(string).GetMethod(nameof(string.EndsWith), new[] { typeof(string) })!, constant),
"~=" => Expression.Call(property, typeof(string).GetMethod(nameof(string.Contains), new[] { typeof(string) })!, constant),
"=" => Expression.Equal(property, constant),
"<" => Expression.LessThan(property, constant),
">" => Expression.GreaterThan(property, constant),
"<=" => Expression.LessThanOrEqual(property, constant),
">=" => Expression.GreaterThanOrEqual(property, constant),
_ => throw new CriteriaException($"Unsupported operator: {operatorSymbol}")
};
}
/// <summary>
/// Creates a sort expression based on the given property name.
/// </summary>
/// <typeparam name="T">The type of the parameter in the expression.</typeparam>
/// <param name="propertyName">The name of the property to sort by.</param>
/// <returns>An expression representing the sorting of the property.</returns>
public static Expression<Func<T, object>> SortBy<T>(string propertyName)
{
var parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(parameter, propertyName);
var conversion = Expression.Convert(property, typeof(object));
return Expression.Lambda<Func<T, object>>(conversion, parameter);
}
}

Ejemplo de Uso


Supongamos que tienes una clase User con las propiedades Age, Name y Status. Puedes construir una expresión para filtrar usuarios dinámicamente:

var tokens = Tokenizer.Tokenize("age>=30|name~=John|status=active");
var parser = new Parser(tokens);
AstNode ast = parser.Parse();
var filter = Evaluator.Evaluate<User>(ast);
var users = new List<User>
{
new User { Age = 25, Name = "Alice", Status = "active" },
new User { Age = 35, Name = "John", Status = "active" },
new User { Age = 40, Name = "Bob", Status = "inactive" }
};
var filteredUsers = users.AsQueryable().Where(filter).ToList();
// Resultado: lista con usuarios que cumplen las condiciones del filtro

Conclusiones


El Evaluator es una herramienta poderosa para evaluar ASTs y construir expresiones dinámicas, ideal para sistemas con necesidades de filtrado flexible. Al abstraer la lógica de construcción de expresiones, facilita el mantenimiento y la extensibilidad del código.