Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve bitwise operations support #208

Merged
merged 4 commits into from
Dec 13, 2021
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
27 changes: 26 additions & 1 deletion src/DynamicExpresso.Core/Parsing/ParseSignatures.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;

namespace DynamicExpresso.Parsing
{
Expand Down Expand Up @@ -79,6 +79,31 @@ public interface INotSignatures
void F(bool? x);
}

public interface IBitwiseComplementSignatures
{
void F(int x, int count);
void F(uint x, int count);
void F(long x, int count);
void F(ulong x, int count);
void F(int? x, int? count);
void F(uint? x, int? count);
void F(long? x, int? count);
void F(ulong? x, int? count);
}

// the signatures are the same for the left and right shifts
public interface IShiftSignatures
{
void F(int x);
void F(uint x);
void F(long x);
void F(ulong x);
void F(int? x);
void F(uint? x);
void F(long? x);
void F(ulong? x);
}

//interface IEnumerableSignatures
//{
// void Where(bool predicate);
Expand Down
62 changes: 58 additions & 4 deletions src/DynamicExpresso.Core/Parsing/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ private Expression ParseComparison()
{
var op = _token;
NextToken();
var right = ParseAdditive();
var right = ParseShift();
var isEquality = op.id == TokenId.DoubleEqual || op.id == TokenId.ExclamationEqual;

//if (isEquality && !left.Type.IsValueType && !right.Type.IsValueType)
Expand Down Expand Up @@ -436,7 +436,7 @@ private Expression ParseComparison()
// is, as operators
private Expression ParseTypeTesting()
{
var left = ParseAdditive();
var left = ParseShift();
while (_token.text == ParserConstants.KeywordIs
|| _token.text == ParserConstants.KeywordAs)
{
Expand Down Expand Up @@ -493,6 +493,47 @@ private Expression ParseAdditive()
return left;
}

// << , >> operators
private Expression ParseShift()
{
var left = ParseAdditive();
while (IsShiftOperator(out var shiftType))
{
NextToken();
var right = ParseAdditive();
CheckAndPromoteOperands(typeof(ParseSignatures.IShiftSignatures), 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()
{
Expand Down Expand Up @@ -525,12 +566,13 @@ private Expression ParseMultiplicative()
// +,-, ! unary operators
private Expression ParseUnary()
{
if (_token.id == TokenId.Minus || _token.id == TokenId.Exclamation || _token.id == TokenId.Plus)
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)
_token.id == TokenId.RealLiteral)
{
if (op.id == TokenId.Minus)
{
Expand Down Expand Up @@ -561,6 +603,11 @@ private Expression ParseUnary()
CheckAndPromoteOperand(typeof(ParseSignatures.INotSignatures), ref expr);
expr = GenerateUnary(ExpressionType.Not, expr);
}
else if (op.id == TokenId.Tilde)
{
CheckAndPromoteOperand(typeof(ParseSignatures.IBitwiseComplementSignatures), ref expr);
expr = GenerateUnary(ExpressionType.OnesComplement, expr);
}
return expr;
}
return ParsePrimary();
Expand All @@ -577,6 +624,7 @@ private Expression GenerateUnary(ExpressionType unaryType, Expression expr)
{
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;
}

Expand Down Expand Up @@ -2503,6 +2551,8 @@ private Expression GenerateBinary(ExpressionType binaryType, Expression left, Ex
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;
Expand Down Expand Up @@ -2722,6 +2772,10 @@ private void NextToken()
NextChar();
t = TokenId.Minus;
break;
case '~':
NextChar();
t = TokenId.Tilde;
break;
case '.':
NextChar();

Expand Down
3 changes: 2 additions & 1 deletion src/DynamicExpresso.Core/Parsing/TokenId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ internal enum TokenId
Plus,
Comma,
Minus,
Tilde,
Dot,
QuestionQuestion,
Slash,
Expand All @@ -38,7 +39,7 @@ internal enum TokenId
Caret,
OpenCurlyBracket,
CloseCurlyBracket,
LambdaArrow
LambdaArrow,
}

}
82 changes: 82 additions & 0 deletions test/DynamicExpresso.UnitTest/OperatorsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,88 @@ public void Unary_Operators()
Assert.AreEqual(-45, target.Eval("-45"));
Assert.AreEqual(5, target.Eval("+5"));
Assert.AreEqual(false, target.Eval("!true"));

Assert.AreEqual(~2, target.Eval("~2"));
Assert.AreEqual(~2ul, target.Eval("~2ul"));
}

[Test]
public void Shift_Operators()
{
var target = new Interpreter();
var x = 0b_1100_1001_0000_0000_0000_0000_0001_0001;
target.SetVariable("x", x);

Assert.AreEqual(x >> 4, target.Eval("x >> 4"));
Assert.AreEqual(x << 4, target.Eval("x << 4"));

// ensure they can be chained
Assert.AreEqual(x >> 1 >> 1 >> 1, target.Eval("x >> 1 >> 1 >> 1"));
Assert.AreEqual(x << 1 << 1 << 1, target.Eval("x << 1 << 1 << 1"));

// ensure priority
Assert.IsFalse(target.Eval<bool>("1 << 4 < 16"));
Assert.IsTrue(target.Eval<bool>("1 << 4 < 17"));
}

[Test]
public void Numeric_Logical_And()
{
var target = new Interpreter();
target.Reference(typeof(Convert));

var a = 0b_1111_1000;
var b = 0b_0001_1100;
target.SetVariable("a", a);
target.SetVariable("b", b);

Assert.AreEqual(a & b, target.Eval("a & b"));
}

[Test]
public void Numeric_Logical_Or()
{
var target = new Interpreter();
target.Reference(typeof(Convert));

var a = 0b_1111_1000;
var b = 0b_0001_1100;
target.SetVariable("a", a);
target.SetVariable("b", b);

Assert.AreEqual(a | b, target.Eval("a | b"));
}

[Test]
public void Numeric_Logical_Xor()
{
var target = new Interpreter();
target.Reference(typeof(Convert));

var a = 0b_1111_1000;
var b = 0b_0001_1100;
target.SetVariable("a", a);
target.SetVariable("b", b);

Assert.AreEqual(a ^ b, target.Eval("a ^ b"));
}

[Test, Ignore("Current operator resolution doesn't lift int to uint")]
public void Bitwise_operations_uint_int()
{
var target = new Interpreter();

// ensure we can resolve operators between uint and int
var x = 0b_1111_1000u;
target.SetVariable("x", x);

Assert.AreEqual(~x, target.Eval<uint>("~x"));
Assert.AreEqual(x >> 4, target.Eval<uint>("x >> 4"));
Assert.AreEqual(x << 4, target.Eval<uint>("x << 4"));

Assert.AreEqual(x & 4, target.Eval<uint>("x & 4"));
Assert.AreEqual(x | 4, target.Eval<uint>("x | 4"));
Assert.AreEqual(x ^ 4, target.Eval<uint>("x ^ 4"));
}

[Test]
Expand Down