Add operator // for division.
In both Python 2 and Python 3, the operator // is used for int division.
With Python 3, operator / is for float division.
RELNOTES: None.
PiperOrigin-RevId: 156582262
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 ea8280a..0e03157 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
@@ -134,6 +134,7 @@
return mult(lval, rval, env, location);
case DIVIDE:
+ case FLOOR_DIVIDE:
return divide(lval, rval, location);
case PERCENT:
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 d7ee319..9257de7 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
@@ -296,7 +296,7 @@
* delimiter (3 x quot), and advances 'pos' by two if so.
*/
private boolean skipTripleQuote(char quot) {
- if (pos + 1 < buffer.length && buffer[pos] == quot && buffer[pos + 1] == quot) {
+ if (lookaheadIs(0, quot) && lookaheadIs(1, quot)) {
pos += 2;
return true;
} else {
@@ -340,7 +340,7 @@
// Insert \ and the following character.
// As in Python, it means that a raw string can never end with a single \.
literal.append('\\');
- if (pos + 1 < buffer.length && buffer[pos] == '\r' && buffer[pos + 1] == '\n') {
+ if (lookaheadIs(0, '\r') && lookaheadIs(1, '\n')) {
literal.append("\n");
pos += 2;
} else if (buffer[pos] == '\r' || buffer[pos] == '\n') {
@@ -356,7 +356,7 @@
pos++;
switch (c) {
case '\r':
- if (pos < buffer.length && buffer[pos] == '\n') {
+ if (lookaheadIs(0, '\n')) {
pos += 1;
break;
} else {
@@ -466,7 +466,7 @@
return t;
case '\\':
if (isRaw) {
- if (pos + 1 < buffer.length && buffer[pos] == '\r' && buffer[pos + 1] == '\n') {
+ if (lookaheadIs(0, '\r') && lookaheadIs(1, '\n')) {
// There was a CRLF after the newline. No shortcut possible, since it needs to be
// transformed into a single LF.
pos = oldPos + 1;
@@ -483,9 +483,10 @@
case '"':
if (c == quot) {
// close-quote, all done.
- return new Token(TokenKind.STRING, oldPos, pos,
- bufferSlice(oldPos + 1, pos - 1));
+ return new Token(TokenKind.STRING, oldPos, pos, bufferSlice(oldPos + 1, pos - 1));
}
+ break;
+ default: // fall out
}
}
@@ -667,6 +668,11 @@
}
}
+ /** Test if the character at pos+p is c. */
+ private boolean lookaheadIs(int p, char c) {
+ return pos + p < buffer.length && buffer[pos + p] == c;
+ }
+
/**
* Performs tokenization of the character buffer of file contents provided to
* the constructor.
@@ -747,7 +753,16 @@
break;
}
case '/': {
- addToken(new Token(TokenKind.SLASH, pos - 1, pos));
+ if (lookaheadIs(0, '/') && lookaheadIs(1, '=')) {
+ addToken(new Token(TokenKind.SLASH_SLASH_EQUALS, pos - 1, pos + 2));
+ pos += 2;
+ } else if (lookaheadIs(0, '/')) {
+ addToken(new Token(TokenKind.SLASH_SLASH, pos - 1, pos + 1));
+ pos += 1;
+ } else {
+ // /= is handled by tokenizeTwoChars.
+ addToken(new Token(TokenKind.SLASH, pos - 1, pos));
+ }
break;
}
case ';': {
@@ -770,9 +785,9 @@
}
case '\\': {
// Backslash character is valid only at the end of a line (or in a string)
- if (pos + 1 < buffer.length && buffer[pos] == '\n') {
+ if (lookaheadIs(0, '\n')) {
pos += 1; // skip the end of line character
- } else if (pos + 2 < buffer.length && buffer[pos] == '\r' && buffer[pos + 1] == '\n') {
+ } else if (lookaheadIs(0, '\r') && lookaheadIs(1, '\n')) {
pos += 2; // skip the CRLF at the end of line
} else {
addToken(new Token(TokenKind.ILLEGAL, pos - 1, pos, Character.toString(c)));
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 91bd49d..b839865 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
@@ -21,6 +21,7 @@
AND("and"),
DIVIDE("/"),
EQUALS_EQUALS("=="),
+ FLOOR_DIVIDE("//"),
GREATER(">"),
GREATER_EQUALS(">="),
IN("in"),
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 23bb0cd..1cb224d 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
@@ -38,7 +38,6 @@
import java.util.List;
import java.util.Map;
-
/**
* Recursive descent parser for LL(2) BUILD language.
* Loosely based on Python 2 grammar.
@@ -159,6 +158,7 @@
.put(TokenKind.OR, Operator.OR)
.put(TokenKind.PERCENT, Operator.PERCENT)
.put(TokenKind.SLASH, Operator.DIVIDE)
+ .put(TokenKind.SLASH_SLASH, Operator.FLOOR_DIVIDE)
.put(TokenKind.PLUS, Operator.PLUS)
.put(TokenKind.PIPE, Operator.PIPE)
.put(TokenKind.STAR, Operator.MULT)
@@ -171,6 +171,7 @@
.put(TokenKind.MINUS_EQUALS, Operator.MINUS)
.put(TokenKind.STAR_EQUALS, Operator.MULT)
.put(TokenKind.SLASH_EQUALS, Operator.DIVIDE)
+ .put(TokenKind.SLASH_SLASH_EQUALS, Operator.FLOOR_DIVIDE)
.put(TokenKind.PERCENT_EQUALS, Operator.PERCENT)
.build();
@@ -185,7 +186,7 @@
Operator.GREATER, Operator.GREATER_EQUALS, Operator.IN, Operator.NOT_IN),
EnumSet.of(Operator.PIPE),
EnumSet.of(Operator.MINUS, Operator.PLUS),
- EnumSet.of(Operator.DIVIDE, Operator.MULT, Operator.PERCENT));
+ EnumSet.of(Operator.DIVIDE, Operator.FLOOR_DIVIDE, Operator.MULT, Operator.PERCENT));
private final Iterator<Token> tokens;
private int errorsCount;
@@ -1198,7 +1199,8 @@
if (token.kind == TokenKind.EQUALS) {
nextToken();
Expression rvalue = parseExpression();
- return setLocation(new AssignmentStatement(expression, rvalue), start, rvalue);
+ return setLocation(
+ new AssignmentStatement(/*lvalue=*/ expression, /*expression=*/ rvalue), start, rvalue);
} else if (augmentedAssignmentMethods.containsKey(token.kind)) {
Operator operator = augmentedAssignmentMethods.get(token.kind);
nextToken();
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 8000546..52acce1 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
@@ -19,9 +19,9 @@
*/
public enum TokenKind {
- ASSERT("assert"),
AND("and"),
AS("as"),
+ ASSERT("assert"),
BREAK("break"),
CLASS("class"),
COLON(":"),
@@ -58,6 +58,7 @@
LESS_EQUALS("<="),
LPAREN("("),
MINUS("-"),
+ MINUS_EQUALS("-="),
NEWLINE("newline"),
NONLOCAL("nonlocal"),
NOT("not"),
@@ -67,13 +68,10 @@
OUTDENT("outdent"),
PASS("pass"),
PERCENT("%"),
+ PERCENT_EQUALS("%="),
PIPE("|"),
PLUS("+"),
PLUS_EQUALS("+="),
- MINUS_EQUALS("-="),
- STAR_EQUALS("*="),
- SLASH_EQUALS("/="),
- PERCENT_EQUALS("%="),
RAISE("raise"),
RBRACE("}"),
RBRACKET("]"),
@@ -81,7 +79,11 @@
RPAREN(")"),
SEMI(";"),
SLASH("/"),
+ SLASH_EQUALS("/="),
+ SLASH_SLASH("//"),
+ SLASH_SLASH_EQUALS("//="),
STAR("*"),
+ STAR_EQUALS("*="),
STAR_STAR("**"),
STRING("string"),
TRY("try"),
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 0f2a2cb..6e826bd 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
@@ -322,6 +322,20 @@
}
@Test
+ public void testFloorDivision() throws Exception {
+ newTest()
+ .testStatement("6 // 2", 3)
+ .testStatement("6 // 4", 1)
+ .testStatement("3 // 6", 0)
+ .testStatement("7 // -2", -4)
+ .testStatement("-7 // 2", -4)
+ .testStatement("-7 // -2", 3)
+ .testStatement("2147483647 // 2", 1073741823)
+ .testIfErrorContains("unsupported operand type(s) for /: 'string' and 'int'", "'str' / 2")
+ .testIfExactError("integer division by zero", "5 // 0");
+ }
+
+ @Test
public void testOperatorPrecedence() throws Exception {
newTest()
.testStatement("2 + 3 * 4", 14)