Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Ramstack.ExpressionParser/Binder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public abstract class Binder
/// </summary>
/// <param name="name">The <see cref="Identifier"/> representing the name of the requested type.</param>
/// <returns>
/// The <see cref="Type"/> instance corresponding to the specified <paramref name="name"/>
/// The <see cref="Type"/> instance corresponding to the specified <paramref name="name"/>
/// if resolved successfully; otherwise, <see langword="null"/>.
/// </returns>
public abstract Type? BindToType(Identifier name);
Expand Down
9 changes: 6 additions & 3 deletions src/Ramstack.ExpressionParser/DefaultBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@ public void RegisterType(Type type, bool importAsStatic = false)
/// <inheritdoc />
public override MemberInfo? BindToMember(Type? type, Identifier memberName, bool isStatic)
{
var bindingFlags = BindingFlags.Public | (isStatic ? BindingFlags.Static : BindingFlags.Instance);
var bindingFlags = isStatic
? BindingFlags.Public | BindingFlags.Static
: BindingFlags.Public | BindingFlags.Instance;

var q = type?.GetMembers(bindingFlags)
?? PredefinedType
Expand Down Expand Up @@ -158,8 +160,9 @@ private static IEnumerable<MethodBase> GetMethods(Type type, string methodName,
? BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.Static
: BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.Instance;

return type.GetMethods(bindingFlags)
.Where(m => StringComparer.OrdinalIgnoreCase.Equals(m.Name, methodName))
return type
.GetMethods(bindingFlags)
.Where(m => string.Equals(m.Name, methodName, StringComparison.OrdinalIgnoreCase))
.Distinct();
}
}
73 changes: 49 additions & 24 deletions src/Ramstack.ExpressionParser/ExpressionBuilder.Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,14 @@ private static List<Expression> CreateInjectingArguments(MethodInfo method, IRea
var paramArrayType = parameter.ParameterType.GetElementType()!;

for (; i < args.Count; i++)
paramArray.Add(Convert(args[i], paramArrayType));
paramArray.Add(
Convert(args[i], paramArrayType));

list.Add(
Expression.NewArrayInit(
paramArrayType,
paramArray));

list.Add(Expression.NewArrayInit(paramArrayType, paramArray));
break;
}

Expand Down Expand Up @@ -112,10 +117,8 @@ private static Expression ApplyBinaryExpression(Identifier op, Func<Expression,
if (enumUnderlyingType is not null)
{
if (lhs.Type != rhs.Type)
{
if (lhs.Type.IsEnum ? enumUnderlyingType != rhs.Type : enumUnderlyingType != lhs.Type)
Error.NonApplicableBinaryOperator(op, lhs.Type, rhs.Type);
}

if (lhs.Type.IsEnum)
lhs = Expression.Convert(lhs, enumUnderlyingType);
Expand All @@ -128,35 +131,35 @@ private static Expression ApplyBinaryExpression(Identifier op, Func<Expression,
{
if (!TypeUtils.IsInteger(lhs.Type) && TypeUtils.IsInteger(rhs.Type))
Error.NonApplicableBinaryOperator(op, lhs.Type, rhs.Type);

if (rhs.Type == typeof(sbyte)
|| rhs.Type == typeof(byte)
|| rhs.Type == typeof(short)
|| rhs.Type == typeof(ushort))
rhs = Expression.Convert(rhs, typeof(int));

if (rhs.Type != typeof(int))
Error.NonApplicableBinaryOperator(op, lhs.Type, rhs.Type);

if (lhs.Type == typeof(sbyte)
|| lhs.Type == typeof(byte)
|| lhs.Type == typeof(short)
|| lhs.Type == typeof(ushort))
lhs = Expression.Convert(lhs, typeof(int));

if (op.Name == ">>>")
{
if (lhs.Type == typeof(int))
return Expression.Convert(
apply(Expression.Convert(lhs, typeof(uint)), rhs),
typeof(int));

if (lhs.Type == typeof(long))
return Expression.Convert(
apply(Expression.Convert(lhs, typeof(ulong)), rhs),
typeof(long));
}

return apply(lhs, rhs);
}

Expand All @@ -183,12 +186,28 @@ private static Expression ApplyBinaryExpression(Identifier op, Func<Expression,
return result;
}

//
// 12.4.7 Numeric promotions
// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#1247-numeric-promotions
//
private static void ApplyBinaryNumericPromotions(Identifier op, ref Expression lhs, ref Expression rhs)
{
//
// 12.4.7.3 Binary numeric promotions
// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#12473-binary-numeric-promotions
//
// Binary numeric promotion implicitly converts both operands to a common type which,
// in case of the non-relational operators, also becomes the result type of the operation.
// Binary numeric promotion consists of applying the following rules, in the order they appear here:
//
// * If either operand is of type decimal, the other operand is converted to type decimal,
// or a binding-time error occurs if the other operand is of type float or double.
// * Otherwise, if either operand is of type double, the other operand is converted to type double.
// * Otherwise, if either operand is of type float, the other operand is converted to type float.
// * Otherwise, if either operand is of type ulong, the other operand is converted to type ulong,
// or a binding-time error occurs if the other operand is of type sbyte, short, int, or long.
// * Otherwise, if either operand is of type long, the other operand is converted to type long.
// * Otherwise, if either operand is of type uint and the other operand is of type sbyte, short, or int,
// both operands are converted to type long.
// * Otherwise, if either operand is of type uint, the other operand is converted to type uint.
// * Otherwise, both operands are converted to type int.

var lhsType = lhs.Type;
var rhsType = rhs.Type;

Expand Down Expand Up @@ -218,8 +237,10 @@ private static void ApplyBinaryNumericPromotions(Identifier op, ref Expression l

if (conversionType == typeof(decimal))
{
//
// If either operand is of type decimal, the other operand is converted to type decimal,
// or a compile-time error occurs if the other operand is of type float or double.
//
if (lhsType == typeof(double)
|| lhsType == typeof(float)
|| rhsType == typeof(double)
Expand All @@ -228,8 +249,10 @@ private static void ApplyBinaryNumericPromotions(Identifier op, ref Expression l
}
else if (conversionType == typeof(ulong))
{
// if either operand is of type ulong, the other operand is converted to type ulong,
//
// If either operand is of type ulong, the other operand is converted to type ulong,
// or a compile-time error occurs if the other operand is of type sbyte, short, int, or long.
//
if (lhsType == typeof(sbyte)
|| lhsType == typeof(short)
|| lhsType == typeof(int)
Expand All @@ -242,8 +265,10 @@ private static void ApplyBinaryNumericPromotions(Identifier op, ref Expression l
}
else if (conversionType == typeof(uint))
{
// if either operand is of type uint and the other operand is of type sbyte, short, or int,
//
// If either operand is of type uint and the other operand is of type sbyte, short, or int,
// both operands are converted to type long.
//
if (lhsType == typeof(sbyte)
|| lhsType == typeof(short)
|| lhsType == typeof(int)
Expand All @@ -263,25 +288,25 @@ private static void ApplyBinaryNumericPromotions(Identifier op, ref Expression l
}
}

private static Func<Expression, Expression, Expression> CreateOperatorFactory(string @operator)
private static Func<Expression, Expression, Expression> ResolveBinaryOperatorFactory(string @operator)
{
return @operator switch
{
"??" => Expression.Coalesce,
"||" => Expression.OrElse,
"&&" => Expression.AndAlso,
"|" => Expression.Or,
"^" => Expression.ExclusiveOr,
"&" => Expression.And,
"||" => Expression.OrElse,
"==" => Expression.Equal,
"!=" => Expression.NotEqual,
"<=" => Expression.LessThanOrEqual,
"<" => Expression.LessThan,
">=" => Expression.GreaterThanOrEqual,
">" => Expression.GreaterThan,
">>" => Expression.RightShift,
">>>" => Expression.RightShift,
"<<" => Expression.LeftShift,
">>>" => Expression.RightShift,
"<" => Expression.LessThan,
">" => Expression.GreaterThan,
"&" => Expression.And,
"|" => Expression.Or,
"^" => Expression.ExclusiveOr,
"+" => Expression.Add,
"-" => Expression.Subtract,
"*" => Expression.Multiply,
Expand Down
34 changes: 17 additions & 17 deletions src/Ramstack.ExpressionParser/ExpressionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,38 +37,38 @@ protected internal override Expression VisitBinary(Expr.Binary expr)
var lhs = Visit(expr.Left);
var rhs = Visit(expr.Right);

var factory = CreateOperatorFactory(expr.Operator.Name);
var lhsType = lhs.Type;
var rhsType = rhs.Type;

var l = lhs;
var r = rhs;
var factory = ResolveBinaryOperatorFactory(expr.Operator.Name);

switch (expr.Operator.Name)
{
case "&&":
case "||":
l = ApplyImplicitConversion(lhs, typeof(bool));
r = ApplyImplicitConversion(rhs, typeof(bool));
lhs = ApplyImplicitConversion(lhs, typeof(bool));
rhs = ApplyImplicitConversion(rhs, typeof(bool));

if (l is null)
Error.MissingImplicitConversion(lhs.Type, typeof(bool));
if (lhs is null)
Error.MissingImplicitConversion(lhsType, typeof(bool));

if (r is null)
Error.MissingImplicitConversion(rhs.Type, typeof(bool));
if (rhs is null)
Error.MissingImplicitConversion(rhsType, typeof(bool));

break;

case "??":
if (lhs.Type.IsValueType && !lhs.Type.IsNullable())
Error.NonApplicableBinaryOperator(expr.Operator, lhs.Type, rhs.Type);
if (lhsType.IsValueType && !lhsType.IsNullable())
Error.NonApplicableBinaryOperator(expr.Operator, lhsType, rhsType);

r = ApplyImplicitConversion(rhs, lhs.Type);
if (r is null)
Error.NonApplicableBinaryOperator(expr.Operator, lhs.Type, rhs.Type);
rhs = ApplyImplicitConversion(rhs, lhsType);
if (rhs is null)
Error.NonApplicableBinaryOperator(expr.Operator, lhsType, rhsType);

break;

case "+":
if (lhs.Type != typeof(string) && rhs.Type != typeof(string))
if (lhsType != typeof(string) && rhsType != typeof(string))
break;

var arguments = new List<Expression>();
Expand Down Expand Up @@ -137,11 +137,11 @@ void Flatten(Expression e)

try
{
return ApplyBinaryExpression(expr.Operator, factory, l, r);
return ApplyBinaryExpression(expr.Operator, factory, lhs, rhs);
}
catch (Exception e) when (e is not ParseErrorException)
{
Error.NonApplicableBinaryOperator(expr.Operator, lhs.Type, rhs.Type);
Error.NonApplicableBinaryOperator(expr.Operator, lhsType, rhsType);
}

return null!;
Expand Down
33 changes: 17 additions & 16 deletions src/Ramstack.ExpressionParser/ExpressionParser.Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ private static Parser<Expr> CreateParser()
).Void();

var number_literal =
ConstantNumberParser.NumericLiteral
ConstantNumberParser
.NumericLiteral
.Do(Expr (v) => new Expr.Literal(v));

var string_literal =
Expand Down Expand Up @@ -187,57 +188,57 @@ static Expr HijackMemberExpression(Expr result, Expr value)

var mul_expression =
prefix_expression.Fold(
OneOf("*/%").ThenIgnore(S).Map(CreateIdentifier),
OneOf("*/%").ThenIgnore(S).Do(CreateIdentifier),
(lhs, rhs, op) => new Expr.Binary(op, lhs, rhs));

var add_expression =
mul_expression.Fold(
OneOf("+-").ThenIgnore(S).Map(CreateIdentifier),
OneOf("+-").ThenIgnore(S).Do(CreateIdentifier),
(lhs, rhs, op) => new Expr.Binary(op, lhs, rhs));

var shift_expression =
add_expression.Fold(
OneOf(["<<", ">>", ">>>"]).ThenIgnore(S).Map(CreateIdentifier),
OneOf(["<<", ">>", ">>>"]).ThenIgnore(S).Do(CreateIdentifier),
(lhs, rhs, op) => new Expr.Binary(op, lhs, rhs));

var relational_expression =
shift_expression.Fold(
OneOf(["<", ">", "<=", ">="]).ThenIgnore(S).Map(CreateIdentifier),
OneOf(["<", ">", "<=", ">="]).ThenIgnore(S).Do(CreateIdentifier),
(lhs, rhs, op) => new Expr.Binary(op, lhs, rhs));

var equality_expression =
relational_expression.Fold(
OneOf("==", "!=").ThenIgnore(S).Map(CreateIdentifier),
OneOf("==", "!=").ThenIgnore(S).Do(CreateIdentifier),
(lhs, rhs, op) => new Expr.Binary(op, lhs, rhs));

var bitwise_and_expression =
equality_expression.Fold(
L('&').ThenIgnore(S).Map(CreateIdentifier),
L('&').ThenIgnore(S).Do(CreateIdentifier),
(lhs, rhs, op) => new Expr.Binary(op, lhs, rhs));

var bitwise_xor_expression =
bitwise_and_expression.Fold(
L('^').ThenIgnore(S).Map(CreateIdentifier),
L('^').ThenIgnore(S).Do(CreateIdentifier),
(lhs, rhs, op) => new Expr.Binary(op, lhs, rhs));

var bitwise_or_expression =
bitwise_xor_expression.Fold(
L('|').ThenIgnore(S).Map(CreateIdentifier),
L('|').ThenIgnore(S).Do(CreateIdentifier),
(lhs, rhs, op) => new Expr.Binary(op, lhs, rhs));

var logical_and_expression =
bitwise_or_expression.Fold(
L("&&").ThenIgnore(S).Map(CreateIdentifier),
L("&&").ThenIgnore(S).Do(CreateIdentifier),
(lhs, rhs, op) => new Expr.Binary(op, lhs, rhs));

var logical_or_expression =
logical_and_expression.Fold(
L("||").ThenIgnore(S).Map(CreateIdentifier),
L("||").ThenIgnore(S).Do(CreateIdentifier),
(lhs, rhs, op) => new Expr.Binary(op, lhs, rhs));

var null_coalesce_expression =
logical_or_expression.FoldR(
L("??").ThenIgnore(S).Map(CreateIdentifier),
L("??").ThenIgnore(S).Do(CreateIdentifier),
(lhs, rhs, op) => new Expr.Binary(op, lhs, rhs));

var conditional_expression = Deferred<Expr>();
Expand All @@ -261,9 +262,9 @@ static Expr HijackMemberExpression(Expr result, Expr value)
return S.Then(expression);
}

private static Identifier CreateIdentifier(Match m, string v) =>
new(v);
private static Identifier CreateIdentifier(string v) =>
new Identifier(v);

private static Identifier CreateIdentifier(Match m, char v) =>
new(new string(v, 1));
private static Identifier CreateIdentifier(char v) =>
new Identifier(v.ToString());
}
2 changes: 1 addition & 1 deletion src/Ramstack.ExpressionParser/Expressions/Expr.Unary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ namespace Ramstack.Parsing.Expressions;
partial class Expr
{
/// <summary>
/// Represents an unary operation expression in an expression tree, consisting of an operator and a single operand.
/// Represents a unary operation expression in an expression tree, consisting of an operator and a single operand.
/// </summary>
/// <param name="operator">The <see cref="Identifier"/> representing the unary operator, e.g., <c>"-"</c> or <c>"int"</c>.</param>
/// <param name="unaryType">The <see cref="UnaryType"/> specifying the kind of unary operation (e.g., arithmetic or conversion).</param>
Expand Down
Loading