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'"));
   }