Build language: Implement integer division
--
MOS_MIGRATED_REVID=91192716
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java
index ac47b63..210d1f5 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java
@@ -184,13 +184,37 @@
break;
}
+ case DIVIDE: {
+ // int / int
+ if (lval instanceof Integer && rval instanceof Integer) {
+ if (rval.equals(0)) {
+ throw new EvalException(getLocation(), "integer division by zero");
+ }
+ // Integer division doesn't give the same result in Java and in Python 2 with
+ // negative numbers.
+ // Java: -7/3 = -2
+ // Python: -7/3 = -3
+ // We want to follow Python semantics, so we use float division and round down.
+ return (int) Math.floor(new Double((Integer) lval) / (Integer) rval);
+ }
+ }
+
case PERCENT: {
// int % int
if (lval instanceof Integer && rval instanceof Integer) {
if (rval.equals(0)) {
throw new EvalException(getLocation(), "integer modulo by zero");
}
- return ((Integer) lval).intValue() % ((Integer) rval).intValue();
+ // Python and Java implement division differently, wrt negative numbers.
+ // In Python, sign of the result is the sign of the divisor.
+ int div = (Integer) rval;
+ int result = ((Integer) lval).intValue() % Math.abs(div);
+ if (result > 0 && div < 0) {
+ result += div; // make the result negative
+ } else if (result < 0 && div > 0) {
+ result += div; // make the result positive
+ }
+ return result;
}
// string % tuple, string % dict, string % anything-else
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Lexer.java b/src/main/java/com/google/devtools/build/lib/syntax/Lexer.java
index 86b8de7..77cdf3f 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Lexer.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Lexer.java
@@ -685,6 +685,10 @@
addToken(new Token(TokenKind.PERCENT, pos - 1, pos));
break;
}
+ case '/': {
+ addToken(new Token(TokenKind.SLASH, pos - 1, pos));
+ break;
+ }
case ';': {
addToken(new Token(TokenKind.SEMI, pos - 1, pos));
break;
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Operator.java b/src/main/java/com/google/devtools/build/lib/syntax/Operator.java
index 628570e..8849583 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Operator.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Operator.java
@@ -19,6 +19,7 @@
public enum Operator {
AND("and"),
+ DIVIDE("/"),
EQUALS_EQUALS("=="),
GREATER(">"),
GREATER_EQUALS(">="),
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
index 75b8db8..e993821 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
@@ -87,6 +87,7 @@
TokenKind.PLUS,
TokenKind.MINUS,
TokenKind.PERCENT,
+ TokenKind.SLASH,
TokenKind.RPAREN,
TokenKind.RBRACKET);
@@ -115,6 +116,7 @@
.put(TokenKind.NOT_EQUALS, Operator.NOT_EQUALS)
.put(TokenKind.OR, Operator.OR)
.put(TokenKind.PERCENT, Operator.PERCENT)
+ .put(TokenKind.SLASH, Operator.DIVIDE)
.put(TokenKind.PLUS, Operator.PLUS)
.put(TokenKind.STAR, Operator.MULT)
.build();
@@ -134,7 +136,7 @@
EnumSet.of(Operator.EQUALS_EQUALS, Operator.NOT_EQUALS, Operator.LESS, Operator.LESS_EQUALS,
Operator.GREATER, Operator.GREATER_EQUALS, Operator.IN),
EnumSet.of(Operator.MINUS, Operator.PLUS),
- EnumSet.of(Operator.MULT, Operator.PERCENT));
+ EnumSet.of(Operator.DIVIDE, Operator.MULT, Operator.PERCENT));
private Iterator<Token> tokens = null;
private int errorsCount;
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/TokenKind.java b/src/main/java/com/google/devtools/build/lib/syntax/TokenKind.java
index 866b9b9..57f5fc4 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/TokenKind.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/TokenKind.java
@@ -74,6 +74,7 @@
RETURN("return"),
RPAREN(")"),
SEMI(";"),
+ SLASH("/"),
STAR("*"),
STAR_STAR("**"),
STRING("string"),
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
index a558805..37042ed 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
@@ -252,6 +252,9 @@
assertThat(eval("6 % 2")).isEqualTo(0);
assertThat(eval("6 % 4")).isEqualTo(2);
assertThat(eval("3 % 6")).isEqualTo(3);
+ assertThat(eval("7 % -4")).isEqualTo(-1);
+ assertThat(eval("-7 % 4")).isEqualTo(1);
+ assertThat(eval("-7 % -4")).isEqualTo(-3);
checkEvalError("integer modulo by zero", "5 % 0");
}
@@ -265,6 +268,25 @@
}
@Test
+ public void testDivision() throws Exception {
+ assertThat(eval("6 / 2")).isEqualTo(3);
+ assertThat(eval("6 / 4")).isEqualTo(1);
+ assertThat(eval("3 / 6")).isEqualTo(0);
+ assertThat(eval("7 / -2")).isEqualTo(-4);
+ assertThat(eval("-7 / 2")).isEqualTo(-4);
+ assertThat(eval("-7 / -2")).isEqualTo(3);
+ assertThat(eval("2147483647 / 2")).isEqualTo(1073741823);
+ checkEvalError("integer division by zero", "5 / 0");
+ }
+
+ @Test
+ public void testOperatorPrecedence() throws Exception {
+ assertThat(eval("2 + 3 * 4")).isEqualTo(14);
+ assertThat(eval("2 + 3 / 4")).isEqualTo(2);
+ assertThat(eval("2 * 3 + 4 / -2")).isEqualTo(4);
+ }
+
+ @Test
public void testConcatStrings() throws Exception {
assertEquals("foobar", eval("'foo' + 'bar'"));
}