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 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
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,18 @@ private static Object evaluate(
case PIPE:
return pipe(x, y, env, location);

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

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

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

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

case MINUS:
return minus(x, y, location);

Expand Down Expand Up @@ -346,7 +358,9 @@ private static Object plus(
/** Implements 'x | y'. */
private static Object pipe(Object x, Object y, Environment env, Location location)
throws EvalException {
if (x instanceof SkylarkNestedSet) {
if (x instanceof Integer && y instanceof Integer) {
return ((Integer) x) | ((Integer) y);
} else if (x instanceof SkylarkNestedSet) {
if (env.getSemantics().incompatibleDepsetUnion()) {
throw new EvalException(
location,
Expand Down Expand Up @@ -447,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
32 changes: 30 additions & 2 deletions src/main/java/com/google/devtools/build/lib/syntax/Lexer.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ public final class Lexer {
.put('*', TokenKind.STAR_EQUALS)
.put('/', TokenKind.SLASH_EQUALS)
.put('%', TokenKind.PERCENT_EQUALS)
.put('^', TokenKind.CARET_EQUALS)
.put('&', TokenKind.AMPERSAND_EQUALS)
.put('|', TokenKind.PIPE_EQUALS)
.build();

private final EventHandler eventHandler;
Expand Down Expand Up @@ -809,10 +812,26 @@ private void tokenize() {
popParen();
break;
case '>':
setToken(TokenKind.GREATER, pos - 1, pos);
if (lookaheadIs(0, '>') && lookaheadIs(1, '=')) {
Quarz0 marked this conversation as resolved.
Show resolved Hide resolved
setToken(TokenKind.GREATER_GREATER_EQUALS, pos - 1, pos + 2);
pos += 2;
} else if (lookaheadIs(0, '>')) {
setToken(TokenKind.GREATER_GREATER, pos - 1, pos + 1);
pos += 1;
} else {
setToken(TokenKind.GREATER, pos - 1, pos);
}
break;
case '<':
setToken(TokenKind.LESS, pos - 1, pos);
if (lookaheadIs(0, '<') && lookaheadIs(1, '=')) {
setToken(TokenKind.LESS_LESS_EQUALS, pos - 1, pos + 2);
pos += 2;
} else if (lookaheadIs(0, '<')) {
setToken(TokenKind.LESS_LESS, pos - 1, pos + 1);
pos += 1;
} else {
setToken(TokenKind.LESS, pos - 1, pos);
}
break;
case ':':
setToken(TokenKind.COLON, pos - 1, pos);
Expand All @@ -835,6 +854,15 @@ private void tokenize() {
case '%':
setToken(TokenKind.PERCENT, pos - 1, pos);
break;
case '~':
setToken(TokenKind.TILDE, pos - 1, pos);
break;
case '&':
setToken(TokenKind.AMPERSAND, pos - 1, pos);
break;
case '^':
setToken(TokenKind.CARET, pos - 1, pos);
break;
case '/':
if (lookaheadIs(0, '/') && lookaheadIs(1, '=')) {
setToken(TokenKind.SLASH_SLASH_EQUALS, pos - 1, pos + 2);
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/google/devtools/build/lib/syntax/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ public enum ParsingLevel {
.put(TokenKind.SLASH_EQUALS, TokenKind.SLASH)
.put(TokenKind.SLASH_SLASH_EQUALS, TokenKind.SLASH_SLASH)
.put(TokenKind.PERCENT_EQUALS, TokenKind.PERCENT)
.put(TokenKind.AMPERSAND_EQUALS, TokenKind.AMPERSAND)
.put(TokenKind.CARET_EQUALS, TokenKind.CARET)
.put(TokenKind.PIPE_EQUALS, TokenKind.PIPE)
.put(TokenKind.GREATER_GREATER_EQUALS, TokenKind.GREATER_GREATER)
.put(TokenKind.LESS_LESS_EQUALS, TokenKind.LESS_LESS)
.build();

/**
Expand All @@ -155,6 +160,9 @@ public enum ParsingLevel {
TokenKind.IN,
TokenKind.NOT_IN),
EnumSet.of(TokenKind.PIPE),
EnumSet.of(TokenKind.CARET),
EnumSet.of(TokenKind.AMPERSAND),
EnumSet.of(TokenKind.GREATER_GREATER, TokenKind.LESS_LESS),
EnumSet.of(TokenKind.MINUS, TokenKind.PLUS),
EnumSet.of(TokenKind.SLASH, TokenKind.SLASH_SLASH, TokenKind.STAR, TokenKind.PERCENT));

Expand Down Expand Up @@ -680,6 +688,13 @@ private Expression parsePrimary() {
UnaryOperatorExpression plus = new UnaryOperatorExpression(TokenKind.PLUS, expr);
return setLocation(plus, start, expr);
}
case TILDE:
{
nextToken();
Expression expr = parsePrimaryWithSuffix();
UnaryOperatorExpression tilde = new UnaryOperatorExpression(TokenKind.TILDE, expr);
return setLocation(tilde, start, expr);
}
default:
{
syntaxError("expected expression");
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/com/google/devtools/build/lib/syntax/TokenKind.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@
*/
public enum TokenKind {

AMPERSAND("&"),
AMPERSAND_EQUALS("&="),
AND("and"),
AS("as"),
ASSERT("assert"),
BREAK("break"),
CARET("^"),
CARET_EQUALS("^="),
CLASS("class"),
COLON(":"),
COMMA(","),
Expand All @@ -42,6 +46,8 @@ public enum TokenKind {
GLOBAL("global"),
GREATER(">"),
GREATER_EQUALS(">="),
GREATER_GREATER(">>"),
GREATER_GREATER_EQUALS(">>="),
IDENTIFIER("identifier"),
IF("if"),
ILLEGAL("illegal character"),
Expand All @@ -55,6 +61,8 @@ public enum TokenKind {
LBRACKET("["),
LESS("<"),
LESS_EQUALS("<="),
LESS_LESS("<<"),
LESS_LESS_EQUALS("<<="),
LOAD("load"),
LPAREN("("),
MINUS("-"),
Expand All @@ -70,6 +78,7 @@ public enum TokenKind {
PERCENT("%"),
PERCENT_EQUALS("%="),
PIPE("|"),
PIPE_EQUALS("|="),
PLUS("+"),
PLUS_EQUALS("+="),
RAISE("raise"),
Expand All @@ -86,6 +95,7 @@ public enum TokenKind {
STAR_EQUALS("*="),
STAR_STAR("**"),
STRING("string literal"),
TILDE("~"),
TRY("try"),
WHILE("while"),
WITH("with"),
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, MINUS or PLUS
private final TokenKind op; // NOT, TILDE, MINUS or PLUS
private final Expression x;

public UnaryOperatorExpression(TokenKind op, Expression x) {
Expand Down Expand Up @@ -63,30 +63,36 @@ 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)));
}
try {
return Math.negateExact((Integer) value);
} catch (ArithmeticException e) {
// Fails for -MIN_INT.
throw new EvalException(loc, e.getMessage());
if (value instanceof Integer) {
try {
return Math.negateExact((Integer) value);
} catch (ArithmeticException e) {
// Fails for -MIN_INT.
throw new EvalException(loc, e.getMessage());
}
}
break;

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

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) {
return ~((Integer) value);
}
Quarz0 marked this conversation as resolved.
Show resolved Hide resolved
return value;
break;

// ignore any other operator and proceed to report an error
default:
throw new AssertionError("Unsupported unary operator: " + op);
}
throw new EvalException(
loc,
String.format(
"unsupported unary operation: %s%s",
op, EvalUtils.getDataTypeName(value)));
}

@Override
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
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,10 @@ public void testAugmentedAssign() throws Exception {
assertThat(parseFile("x *= 1").toString()).isEqualTo("[x *= 1\n]");
assertThat(parseFile("x /= 1").toString()).isEqualTo("[x /= 1\n]");
assertThat(parseFile("x %= 1").toString()).isEqualTo("[x %= 1\n]");
assertThat(parseFile("x |= 1").toString()).isEqualTo("[x |= 1\n]");
assertThat(parseFile("x &= 1").toString()).isEqualTo("[x &= 1\n]");
assertThat(parseFile("x <<= 1").toString()).isEqualTo("[x <<= 1\n]");
assertThat(parseFile("x >>= 1").toString()).isEqualTo("[x >>= 1\n]");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1508,7 +1508,7 @@ public void testUnionSet() throws Exception {
new SkylarkTest("--incompatible_depset_union=false")
.testStatement("str(depset([1, 3]) | depset([1, 2]))", "depset([1, 2, 3])")
.testStatement("str(depset([1, 2]) | [1, 3])", "depset([1, 2, 3])")
.testIfExactError("unsupported operand type(s) for |: 'int' and 'int'", "2 | 4");
.testIfExactError("unsupported operand type(s) for |: 'int' and 'bool'", "2 | False");
Quarz0 marked this conversation as resolved.
Show resolved Hide resolved
}

@Test
Expand Down
63 changes: 63 additions & 0 deletions src/test/starlark/testdata/int.sky
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,66 @@ assert_eq(-+-4, 4)
1 // 0 ### integer division by zero
---
1 % 0 ### integer modulo by zero
---

# bitwise

def f():
x = 2
x &= 1
assert_eq(x, 0)
x = 0
x |= 2
assert_eq(x, 2)
x ^= 3
assert_eq(x, 1)
x <<= 2
assert_eq(x, 4)
x >>=2
assert_eq(x, 1)

f()
---
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