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

Support bitwise operations #8903

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
Expand Up @@ -190,16 +190,16 @@ private static Object evaluate(
return pipe(x, y, env, location);

case AMPERSAND:
return (Integer) x & (Integer) y;
return and(x, y, location);
Quarz0 marked this conversation as resolved.
Show resolved Hide resolved

case CARET:
return (Integer) x ^ (Integer) y;
return xor(x, y, location);

case GREATER_GREATER:
Quarz0 marked this conversation as resolved.
Show resolved Hide resolved
return (Integer) x >> (Integer) y;
return rightShift(x, y, location);

case LESS_LESS:
return (Integer) x << (Integer) y;
return leftShift(x, y, location);

case MINUS:
return minus(x, y, location);
Expand Down Expand Up @@ -461,6 +461,50 @@ private static Object percent(Object x, Object y, Location location) throws Eval
throw typeException(x, y, TokenKind.PERCENT, location);
}

/** Implements 'x & y'. */
private static Object and(Object x, Object y, Location location) throws EvalException {
if (x instanceof Integer && y instanceof Integer) {
return (Integer) x & (Integer) y;
}
throw typeException(x, y, TokenKind.AMPERSAND, location);
}

/** Implements 'x ^ y'. */
private static Object xor(Object x, Object y, Location location) throws EvalException {
if (x instanceof Integer && y instanceof Integer) {
return (Integer) x ^ (Integer) y;
}
throw typeException(x, y, TokenKind.CARET, location);
}

/** Implements 'x >> y'. */
private static Object rightShift(Object x, Object y, Location location) throws EvalException {
if (x instanceof Integer && y instanceof Integer) {
if ((Integer) y < 0) {
throw new EvalException(location, "negative shift count: " + y);
} else if ((Integer) y >= Integer.SIZE) {
return ((Integer) x < 0) ? -1 : 0;
}
return (Integer) x >> (Integer) y;
}
throw typeException(x, y, TokenKind.GREATER_GREATER, location);
}

/** Implements 'x << y'. */
private static Object leftShift(Object x, Object y, Location location) throws EvalException {
if (x instanceof Integer && y instanceof Integer) {
if ((Integer) y < 0) {
throw new EvalException(location, "negative shift count: " + y);
}
Integer result = (Integer) x << (Integer) y;
if (!rightShift(result, y, location).equals(x)) {
throw new ArithmeticException("integer overflow");
}
return result;
}
throw typeException(x, y, TokenKind.LESS_LESS, location);
}

/** Throws an exception signifying incorrect types for the given operator. */
private static EvalException typeException(Object x, Object y, TokenKind op, Location location) {
// NB: this message format is identical to that used by CPython 2.7.6 or 3.4.0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,13 @@ private Expression parsePrimary() {
UnaryOperatorExpression minus = new UnaryOperatorExpression(TokenKind.MINUS, expr);
return setLocation(minus, start, expr);
}
case PLUS:
{
nextToken();
Expression expr = parsePrimaryWithSuffix();
UnaryOperatorExpression plus = new UnaryOperatorExpression(TokenKind.PLUS, expr);
return setLocation(plus, start, expr);
}
case TILDE:
{
nextToken();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
/** A UnaryOperatorExpression represents a unary operator expression, 'op x'. */
public final class UnaryOperatorExpression extends Expression {

private final TokenKind op; // NOT, TILDE or MINUS
private final TokenKind op; // NOT, TILDE, MINUS or PLUS
private final Expression x;

public UnaryOperatorExpression(TokenKind op, Expression x) {
Expand Down Expand Up @@ -63,29 +63,31 @@ private static Object evaluate(TokenKind op, Object value, Location loc)
return !EvalUtils.toBoolean(value);

case MINUS:
if (!(value instanceof Integer)) {
throw new EvalException(
loc,
String.format(
"unsupported operand type for -: '%s'", EvalUtils.getDataTypeName(value)));
if (value instanceof Integer) {
try {
return Math.negateExact((Integer) value);
} catch (ArithmeticException e) {
// Fails for -MIN_INT.
throw new EvalException(loc, e.getMessage());
}
}
try {
return Math.negateExact((Integer) value);
} catch (ArithmeticException e) {
// Fails for -MIN_INT.
throw new EvalException(loc, e.getMessage());

Quarz0 marked this conversation as resolved.
Show resolved Hide resolved
case PLUS:
if (value instanceof Integer) {
return value;
}

Quarz0 marked this conversation as resolved.
Show resolved Hide resolved
case TILDE:
Quarz0 marked this conversation as resolved.
Show resolved Hide resolved
if (!(value instanceof Integer)) {
throw new EvalException(
loc,
String.format(
"unsupported operand type for ~: '%s'", EvalUtils.getDataTypeName(value)));
if (value instanceof Integer) {
return ~((Integer) value);
}
Quarz0 marked this conversation as resolved.
Show resolved Hide resolved
return ~((Integer) value);

default:
Quarz0 marked this conversation as resolved.
Show resolved Hide resolved
throw new AssertionError("Unsupported unary operator: " + op);
throw new EvalException(
Quarz0 marked this conversation as resolved.
Show resolved Hide resolved
loc,
String.format(
"unsupported unary operation: %s'%s'",
Quarz0 marked this conversation as resolved.
Show resolved Hide resolved
op, EvalUtils.getDataTypeName(value)));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public void testExprs() throws Exception {
.testStatement("8 % 3", 2)
.testIfErrorContains("unsupported operand type(s) for %: 'int' and 'string'", "3 % 'foo'")
.testStatement("-5", -5)
.testIfErrorContains("unsupported operand type for -: 'string'", "-'foo'");
.testIfErrorContains("unsupported unary operation: -'string'", "-'foo'");
}

@Test
Expand Down Expand Up @@ -213,31 +213,6 @@ public void testFloorDivision() throws Exception {
.testIfExactError("integer division by zero", "5 // 0");
}

@Test
public void testBitwiseOperations() throws Exception {
newTest()
.testStatement("-6 ^ 0", -6)
.testStatement("6 ^ 6", 0)
.testStatement("1 ^ 6", 7)
.testStatement("7 & 0", 0)
.testStatement("7 & 7", 7)
.testStatement("7 & 2", 2)
.testStatement("7 | 0", 7)
.testStatement("7 | 7", 7)
.testStatement("5 | 2", 7)
.testStatement("2 >> 1", 1)
.testStatement("7 >> 1", 3)
.testStatement("7 >> 0", 7)
.testStatement("0 >> 0", 0)
.testStatement("2 << 1", 4)
.testStatement("7 << 1", 14)
.testStatement("7 << 0", 7)
.testStatement("2147483647 << 2147483647", -2147483648)
.testStatement("~6", -7)
.testStatement("~0", -1)
.testStatement("~2147483647", -2147483648);
}

@Test
public void testCheckedArithmetic() throws Exception {
new SkylarkTest()
Expand All @@ -255,9 +230,7 @@ public void testOperatorPrecedence() throws Exception {
newTest()
.testStatement("2 + 3 * 4", 14)
.testStatement("2 + 3 // 4", 2)
.testStatement("2 * 3 + 4 // -2", 4)
.testStatement("8 | 3 ^ 4 & -2", 15)
.testStatement("~8 >> 1 | 3 ^ 4 & -2 << 2 * 3 + 4 // -2", -5);
.testStatement("2 * 3 + 4 // -2", 4);
}

@Test
Expand Down
36 changes: 34 additions & 2 deletions src/test/starlark/testdata/int.sky
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,46 @@ def f():

f()
---
assert_eq(1|2, 3)
assert_eq(3|6, 7)
assert_eq(1 | 2, 3)
assert_eq(3 | 6, 7)
assert_eq(7 | 0, 7)
assert_eq(7 & 0, 0)
assert_eq(7 & 7, 7)
assert_eq(7 & 2, 2)
assert_eq((1|2) & (2|4), 2)
assert_eq(1 ^ 2, 3)
assert_eq(2 ^ 2, 0)
assert_eq(-6 ^ 0, -6)
assert_eq(1 | 0 ^ 1, 1) # check | and ^ operators precedence
assert_eq(~1, -2)
assert_eq(~-2, 1)
assert_eq(~0, -1)
assert_eq(~6, -7)
assert_eq(~0, -1)
assert_eq(~2147483647, -2147483647 - 1);
assert_eq(1 << 2, 4)
assert_eq(7 << 0, 7)
assert_eq(-1 << 31, -2147483647 - 1)
assert_eq(2 >> 1, 1)
assert_eq(7 >> 0, 7)
assert_eq(0 >> 0, 0)
assert_eq(1000 >> 100, 0)
assert_eq(-10 >> 1000, -1)

# precedence

assert_eq(8 | 3 ^ 4 & -2, 15)
assert_eq(~8 >> 1 | 3 ^ 4 & -2 << 2 * 3 + 4 // -2, -5)

---
1 & False ### unsupported operand type(s) for &: 'int' and 'bool'
---
"a" ^ 5 ### unsupported operand type(s) for ^: 'string' and 'int'
---
~False ### unsupported unary operation: ~'bool'
---
1 << 31 ### integer overflow
---
1 << -4 ### negative shift count: -4
---
2 >> -1 ### negative shift count: -1