Skip to content

Commit

Permalink
Implement ConditionalExpression
Browse files Browse the repository at this point in the history
Also add tests.

--
MOS_MIGRATED_REVID=88474801
  • Loading branch information
fare authored and hanwen committed Mar 13, 2015
1 parent 13459b4 commit 6fc5ee7
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -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());
}
}
20 changes: 19 additions & 1 deletion src/main/java/com/google/devtools/build/lib/syntax/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,8 @@ private <NODE extends ASTNode> NODE setLocation(NODE node, int startOffset, int

// 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());
}

Expand Down Expand Up @@ -908,7 +910,23 @@ private Expression optimizeBinOpExpression(
}

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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,11 @@ public void visit(NotExpression node) {
public void visit(Comment node) {
}

public void visit(ConditionalExpression node) {
visit(node.getThenCase());
visit(node.getCondition());
if (node.getElseCase() != null) {
visit(node.getElseCase());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,19 @@ public void testGreaterThan() throws Exception {
assertEquals(true, eval("'c' > 'a'"));
}

@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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -877,4 +877,28 @@ public void testListConcatenation() throws Exception {}
@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();
}
}

0 comments on commit 6fc5ee7

Please sign in to comment.