aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/ConditionalExpression.java70
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/Parser.java20
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/SyntaxTreeVisitor.java7
-rw-r--r--src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java13
-rw-r--r--src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java24
5 files changed, 133 insertions, 1 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ConditionalExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/ConditionalExpression.java
new file mode 100644
index 0000000000..46471b363f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ConditionalExpression.java
@@ -0,0 +1,70 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.syntax;
+
+/**
+ * Syntax node for an if/else expression.
+ */
+public final class ConditionalExpression extends Expression {
+
+ // Python conditional expressions: $thenCase if $condition else $elseCase
+ // https://docs.python.org/3.5/reference/expressions.html#conditional-expressions
+ private final Expression thenCase;
+ private final Expression condition;
+ private final Expression elseCase;
+
+ public Expression getThenCase() { return thenCase; }
+ public Expression getCondition() { return condition; }
+ public Expression getElseCase() { return elseCase; }
+
+ /**
+ * Constructor for a conditional expression
+ */
+ public ConditionalExpression(
+ Expression thenCase, Expression condition, Expression elseCase) {
+ this.thenCase = thenCase;
+ this.condition = condition;
+ this.elseCase = elseCase;
+ }
+
+ /**
+ * Constructs a string representation of the if expression
+ */
+ @Override
+ public String toString() {
+ return thenCase + " if " + condition + " else " + elseCase;
+ }
+
+ @Override
+ Object eval(Environment env) throws EvalException, InterruptedException {
+ if (EvalUtils.toBoolean(condition.eval(env))) {
+ return thenCase.eval(env);
+ } else {
+ return elseCase.eval(env);
+ }
+ }
+
+ @Override
+ public void accept(SyntaxTreeVisitor visitor) {
+ visitor.visit(this);
+ }
+
+ @Override
+ SkylarkType validate(ValidationEnvironment env) throws EvalException {
+ condition.validate(env);
+ return thenCase.validate(env)
+ .infer(elseCase.validate(env), "else case", thenCase.getLocation(), elseCase.getLocation());
+ }
+}
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 97a4bbd015..90f79ec92c 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
@@ -341,6 +341,8 @@ class Parser {
// Convenience method that uses end offset from the last node.
private <NODE extends ASTNode> NODE setLocation(NODE node, int startOffset, ASTNode lastNode) {
+ Preconditions.checkNotNull(lastNode, "can't extract end offset from a null node");
+ Preconditions.checkNotNull(lastNode.getLocation(), "lastNode doesn't have a location");
return setLocation(node, startOffset, lastNode.getLocation().getEndOffset());
}
@@ -908,7 +910,23 @@ class Parser {
}
private Expression parseExpression() {
- return parseExpression(0);
+ int start = token.left;
+ Expression expr = parseExpression(0);
+ if (token.kind == TokenKind.IF) {
+ nextToken();
+ Expression condition = parseExpression(0);
+ if (token.kind == TokenKind.ELSE) {
+ nextToken();
+ Expression elseClause = parseExpression();
+ return setLocation(new ConditionalExpression(expr, condition, elseClause),
+ start, elseClause);
+ } else {
+ reportError(lexer.createLocation(start, token.left),
+ "missing else clause in conditional expression or semicolon before if");
+ return expr; // Try to recover from error: drop the if and the expression after it. Ouch.
+ }
+ }
+ return expr;
}
private Expression parseExpression(int prec) {
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SyntaxTreeVisitor.java b/src/main/java/com/google/devtools/build/lib/syntax/SyntaxTreeVisitor.java
index e53a7aa9ed..2463d25600 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SyntaxTreeVisitor.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SyntaxTreeVisitor.java
@@ -140,4 +140,11 @@ public class SyntaxTreeVisitor {
public void visit(Comment node) {
}
+ public void visit(ConditionalExpression node) {
+ visit(node.getThenCase());
+ visit(node.getCondition());
+ if (node.getElseCase() != null) {
+ visit(node.getElseCase());
+ }
+ }
}
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 342f34e755..3fd5508922 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
@@ -200,6 +200,19 @@ public class EvaluationTest extends AbstractEvaluationTestCase {
}
@Test
+ public void testConditionalExpressions() throws Exception {
+ assertEquals(1, eval("1 if True else 2"));
+ assertEquals(2, eval("1 if False else 2"));
+ assertEquals(3, eval("1 + 2 if 3 + 4 else 5 + 6"));
+
+ syntaxEvents.setFailFast(false);
+ parseExpr("1 if 2");
+ syntaxEvents.assertContainsEvent(
+ "missing else clause in conditional expression or semicolon before if");
+ syntaxEvents.collector().clear();
+ }
+
+ @Test
public void testCompareStringInt() throws Exception {
checkEvalError("'a' >= 1", "Cannot compare string with int");
}
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
index 3e0b0f3285..b0735ae754 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
@@ -877,4 +877,28 @@ public class SkylarkEvaluationTest extends EvaluationTest {
@Override
@Test
public void testKeywordArgs() {}
+
+ @Test
+ public void testConditionalExpressionAtToplevel() throws Exception {
+ exec(parseFileForSkylark("x = 1 if 2 else 3"), env);
+ assertEquals(1, env.lookup("x"));
+ }
+
+ @Test
+ public void testConditionalExpressionInFunction() throws Exception {
+ exec(parseFileForSkylark(
+ "def foo(a, b, c):\n"
+ + " return a+b if c else a-b\n"
+ + "x = foo(23, 5, 0)"), env);
+ assertEquals(18, env.lookup("x"));
+ }
+
+ @Test
+ public void testBadConditionalExpressionInFunction() throws Exception {
+ syntaxEvents.setFailFast(false);
+ parseFileForSkylark("def foo(a): return [] if a else 0\n");
+ syntaxEvents.assertContainsEvent(
+ "bad else case: int is incompatible with list at /some/file.txt:1:33");
+ syntaxEvents.collector().clear();
+ }
}