diff --git a/src/main/java/com/google/api/generator/engine/ast/AstNodeVisitor.java b/src/main/java/com/google/api/generator/engine/ast/AstNodeVisitor.java index 75ed3b09b2..63ff726e20 100644 --- a/src/main/java/com/google/api/generator/engine/ast/AstNodeVisitor.java +++ b/src/main/java/com/google/api/generator/engine/ast/AstNodeVisitor.java @@ -53,6 +53,8 @@ public interface AstNodeVisitor { public void visit(ArithmeticOperationExpr arithmeticOperationExpr); + public void visit(UnaryOperationExpr unaryOperationExpr); + /** =============================== COMMENT =============================== */ public void visit(LineComment lineComment); diff --git a/src/main/java/com/google/api/generator/engine/ast/TypeNode.java b/src/main/java/com/google/api/generator/engine/ast/TypeNode.java index d1bb85c552..db4e3da779 100644 --- a/src/main/java/com/google/api/generator/engine/ast/TypeNode.java +++ b/src/main/java/com/google/api/generator/engine/ast/TypeNode.java @@ -125,6 +125,15 @@ public static boolean isReferenceType(TypeNode type) { && !type.equals(TypeNode.NULL); } + public static boolean isNumericType(TypeNode type) { + return type.equals(TypeNode.INT) + || type.equals(TypeNode.LONG) + || type.equals(TypeNode.DOUBLE) + || type.equals(TypeNode.SHORT) + || type.equals(TypeNode.FLOAT) + || type.equals(TypeNode.CHAR); + } + public boolean isPrimitiveType() { return isPrimitiveType(typeKind()); } diff --git a/src/main/java/com/google/api/generator/engine/ast/UnaryOperationExpr.java b/src/main/java/com/google/api/generator/engine/ast/UnaryOperationExpr.java new file mode 100644 index 0000000000..23af8c220c --- /dev/null +++ b/src/main/java/com/google/api/generator/engine/ast/UnaryOperationExpr.java @@ -0,0 +1,100 @@ +// Copyright 2020 Google LLC +// +// 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.api.generator.engine.ast; + +import com.google.auto.value.AutoValue; +import com.google.common.base.Preconditions; + +@AutoValue +public abstract class UnaryOperationExpr implements OperationExpr { + + public abstract Expr expr(); + + public abstract OperatorKind operatorKind(); + + public abstract TypeNode type(); + + public void accept(AstNodeVisitor visitor) { + visitor.visit(this); + } + + public static UnaryOperationExpr postfixIncrementWithExpr(Expr expr) { + return builder() + .setExpr(expr) + .setOperatorKind(OperatorKind.UNARY_POST_INCREMENT) + .setType(expr.type()) + .build(); + } + + public static UnaryOperationExpr logicalNotWithExpr(Expr expr) { + return builder() + .setOperatorKind(OperatorKind.UNARY_LOGICAL_NOT) + .setExpr(expr) + .setType(TypeNode.BOOLEAN) + .build(); + } + + private static Builder builder() { + return new AutoValue_UnaryOperationExpr.Builder(); + } + + @AutoValue.Builder + abstract static class Builder { + + // Private setter. + abstract Builder setExpr(Expr expr); + + // Private setter. + abstract Builder setOperatorKind(OperatorKind operator); + + // Private setter. + abstract Builder setType(TypeNode type); + + abstract UnaryOperationExpr autoBuild(); + + private UnaryOperationExpr build() { + UnaryOperationExpr unaryOperationExpr = autoBuild(); + TypeNode exprType = unaryOperationExpr.expr().type(); + OperatorKind operator = unaryOperationExpr.operatorKind(); + final String errorMsg = + String.format( + "Unary operator %s can not be applied to %s. ", operator, exprType.toString()); + + Preconditions.checkState( + !exprType.equals(TypeNode.VOID) && !exprType.equals(TypeNode.NULL), errorMsg); + + if (operator.equals(OperatorKind.UNARY_LOGICAL_NOT)) { + Preconditions.checkState(isValidLogicalNotType(exprType), errorMsg); + } + + if (operator.equals(OperatorKind.UNARY_POST_INCREMENT)) { + Preconditions.checkState(isValidIncrementType(exprType), errorMsg); + } + + return unaryOperationExpr; + } + } + + private static boolean isValidLogicalNotType(TypeNode exprType) { + // Logical not (!) can only be applied on boolean/Boolean type + return exprType.equals(TypeNode.BOOLEAN); + } + + private static boolean isValidIncrementType(TypeNode exprType) { + // Increment (++) can be applied on numeric types(int, double, float, long, short, char) + // and its boxed type (exclude Boolean) + return TypeNode.isNumericType(exprType); + } +} diff --git a/src/main/java/com/google/api/generator/engine/writer/ImportWriterVisitor.java b/src/main/java/com/google/api/generator/engine/writer/ImportWriterVisitor.java index 851e733446..89d488bcd3 100644 --- a/src/main/java/com/google/api/generator/engine/writer/ImportWriterVisitor.java +++ b/src/main/java/com/google/api/generator/engine/writer/ImportWriterVisitor.java @@ -46,6 +46,7 @@ import com.google.api.generator.engine.ast.ThrowExpr; import com.google.api.generator.engine.ast.TryCatchStatement; import com.google.api.generator.engine.ast.TypeNode; +import com.google.api.generator.engine.ast.UnaryOperationExpr; import com.google.api.generator.engine.ast.ValueExpr; import com.google.api.generator.engine.ast.VariableExpr; import com.google.api.generator.engine.ast.WhileStatement; @@ -218,6 +219,11 @@ public void visit(ArithmeticOperationExpr arithmeticOperationExpr) { arithmeticOperationExpr.rhsExpr().accept(this); } + @Override + public void visit(UnaryOperationExpr unaryOperationExpr) { + unaryOperationExpr.expr().accept(this); + } + /** =============================== STATEMENTS =============================== */ @Override public void visit(ExprStatement exprStatement) { diff --git a/src/main/java/com/google/api/generator/engine/writer/JavaWriterVisitor.java b/src/main/java/com/google/api/generator/engine/writer/JavaWriterVisitor.java index e73ddb4e38..9231997bf8 100644 --- a/src/main/java/com/google/api/generator/engine/writer/JavaWriterVisitor.java +++ b/src/main/java/com/google/api/generator/engine/writer/JavaWriterVisitor.java @@ -47,6 +47,7 @@ import com.google.api.generator.engine.ast.TryCatchStatement; import com.google.api.generator.engine.ast.TypeNode; import com.google.api.generator.engine.ast.TypeNode.TypeKind; +import com.google.api.generator.engine.ast.UnaryOperationExpr; import com.google.api.generator.engine.ast.ValueExpr; import com.google.api.generator.engine.ast.Variable; import com.google.api.generator.engine.ast.VariableExpr; @@ -394,6 +395,17 @@ public void visit(ArithmeticOperationExpr arithmeticOperationExpr) { arithmeticOperationExpr.rhsExpr().accept(this); } + @Override + public void visit(UnaryOperationExpr unaryOperationExpr) { + if (unaryOperationExpr.operatorKind().isPrefixOperator()) { + operator(unaryOperationExpr.operatorKind()); + unaryOperationExpr.expr().accept(this); + } else { + unaryOperationExpr.expr().accept(this); + operator(unaryOperationExpr.operatorKind()); + } + } + /** =============================== STATEMENTS =============================== */ @Override public void visit(ExprStatement exprStatement) { diff --git a/src/test/java/com/google/api/generator/engine/ast/BUILD.bazel b/src/test/java/com/google/api/generator/engine/ast/BUILD.bazel index dc98a1d185..ba7583ab22 100644 --- a/src/test/java/com/google/api/generator/engine/ast/BUILD.bazel +++ b/src/test/java/com/google/api/generator/engine/ast/BUILD.bazel @@ -34,6 +34,7 @@ TESTS = [ "SuperObjectValueTest", "WhileStatementTest", "ArithmeticOperationExprTest", + "UnaryOperationExprTest", ] filegroup( diff --git a/src/test/java/com/google/api/generator/engine/ast/UnaryOperationExprTest.java b/src/test/java/com/google/api/generator/engine/ast/UnaryOperationExprTest.java new file mode 100644 index 0000000000..c2321d1577 --- /dev/null +++ b/src/test/java/com/google/api/generator/engine/ast/UnaryOperationExprTest.java @@ -0,0 +1,95 @@ +// Copyright 2020 Google LLC +// +// 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.api.generator.engine.ast; + +import static org.junit.Assert.assertThrows; + +import org.junit.Test; + +public class UnaryOperationExprTest { + /** =============================== Logic Not Operation Expr =============================== */ + @Test + public void logicalNotOperationExpr_validBasic() { + VariableExpr variableExpr = + VariableExpr.withVariable( + Variable.builder().setName("x").setType(TypeNode.BOOLEAN).build()); + UnaryOperationExpr.logicalNotWithExpr(variableExpr); + // No exception thrown, we're good. + } + + @Test + public void logicalNot_validBoxedType() { + VariableExpr variableExpr = + VariableExpr.withVariable( + Variable.builder().setName("x").setType(TypeNode.BOOLEAN_OBJECT).build()); + UnaryOperationExpr.logicalNotWithExpr(variableExpr); + // No exception thrown, we're good. + } + + @Test + public void logicalNot_invalidNumericType() { + VariableExpr variableExpr = + VariableExpr.withVariable(Variable.builder().setName("x").setType(TypeNode.INT).build()); + assertThrows( + IllegalStateException.class, () -> UnaryOperationExpr.logicalNotWithExpr(variableExpr)); + } + + @Test + public void logicalNot_invalidReferenceType() { + VariableExpr variableExpr = + VariableExpr.withVariable(Variable.builder().setName("x").setType(TypeNode.STRING).build()); + assertThrows( + IllegalStateException.class, () -> UnaryOperationExpr.logicalNotWithExpr(variableExpr)); + } + + /** + * =============================== Post Increment Operation Expr =============================== + */ + @Test + public void postIncrement_validBasic() { + VariableExpr variableExpr = + VariableExpr.withVariable(Variable.builder().setName("x").setType(TypeNode.INT).build()); + UnaryOperationExpr.postfixIncrementWithExpr(variableExpr); + // No exception thrown, we're good. + } + + @Test + public void postIncrement_validBoxedType() { + VariableExpr variableExpr = + VariableExpr.withVariable( + Variable.builder().setName("x").setType(TypeNode.FLOAT_OBJECT).build()); + UnaryOperationExpr.postfixIncrementWithExpr(variableExpr); + // No exception thrown, we're good. + } + + @Test + public void postIncrement_invalidBoxedBooleanType() { + VariableExpr variableExpr = + VariableExpr.withVariable( + Variable.builder().setName("x").setType(TypeNode.BOOLEAN_OBJECT).build()); + assertThrows( + IllegalStateException.class, + () -> UnaryOperationExpr.postfixIncrementWithExpr(variableExpr)); + } + + @Test + public void postIncrement_invalidReferenceType() { + VariableExpr variableExpr = + VariableExpr.withVariable(Variable.builder().setName("x").setType(TypeNode.STRING).build()); + assertThrows( + IllegalStateException.class, + () -> UnaryOperationExpr.postfixIncrementWithExpr(variableExpr)); + } +} diff --git a/src/test/java/com/google/api/generator/engine/writer/ImportWriterVisitorTest.java b/src/test/java/com/google/api/generator/engine/writer/ImportWriterVisitorTest.java index bd9c9198ec..d930749ef0 100644 --- a/src/test/java/com/google/api/generator/engine/writer/ImportWriterVisitorTest.java +++ b/src/test/java/com/google/api/generator/engine/writer/ImportWriterVisitorTest.java @@ -44,6 +44,7 @@ import com.google.api.generator.engine.ast.ThisObjectValue; import com.google.api.generator.engine.ast.ThrowExpr; import com.google.api.generator.engine.ast.TypeNode; +import com.google.api.generator.engine.ast.UnaryOperationExpr; import com.google.api.generator.engine.ast.ValueExpr; import com.google.api.generator.engine.ast.VaporReference; import com.google.api.generator.engine.ast.Variable; @@ -867,6 +868,32 @@ public void writeSynchronizedStatementImports_basicVariableExpr() { "import java.util.Map;\n\n")); } + @Test + public void writeUnaryOperationExprImports_LogicalNot() { + MethodInvocationExpr expr = + MethodInvocationExpr.builder() + .setStaticReferenceType(TypeNode.withReference(ConcreteReference.withClazz(Expr.class))) + .setMethodName("isEmpty") + .setReturnType(TypeNode.BOOLEAN) + .build(); + UnaryOperationExpr unaryOperationExpr = UnaryOperationExpr.logicalNotWithExpr(expr); + unaryOperationExpr.accept(writerVisitor); + assertEquals(writerVisitor.write(), "import com.google.api.generator.engine.ast.Expr;\n\n"); + } + + @Test + public void writeUnaryOperationExprImports_PostIncrement() { + MethodInvocationExpr expr = + MethodInvocationExpr.builder() + .setStaticReferenceType(TypeNode.withReference(ConcreteReference.withClazz(Expr.class))) + .setMethodName("getNumber") + .setReturnType(TypeNode.INT) + .build(); + UnaryOperationExpr unaryOperationExpr = UnaryOperationExpr.postfixIncrementWithExpr(expr); + unaryOperationExpr.accept(writerVisitor); + assertEquals(writerVisitor.write(), "import com.google.api.generator.engine.ast.Expr;\n\n"); + } + private static TypeNode createType(Class clazz) { return TypeNode.withReference(ConcreteReference.withClazz(clazz)); } diff --git a/src/test/java/com/google/api/generator/engine/writer/JavaWriterVisitorTest.java b/src/test/java/com/google/api/generator/engine/writer/JavaWriterVisitorTest.java index 247b429002..6df5937936 100644 --- a/src/test/java/com/google/api/generator/engine/writer/JavaWriterVisitorTest.java +++ b/src/test/java/com/google/api/generator/engine/writer/JavaWriterVisitorTest.java @@ -54,6 +54,7 @@ import com.google.api.generator.engine.ast.ThrowExpr; import com.google.api.generator.engine.ast.TryCatchStatement; import com.google.api.generator.engine.ast.TypeNode; +import com.google.api.generator.engine.ast.UnaryOperationExpr; import com.google.api.generator.engine.ast.Value; import com.google.api.generator.engine.ast.ValueExpr; import com.google.api.generator.engine.ast.VaporReference; @@ -1975,6 +1976,29 @@ public void writeSuperObjectValue_accessFieldAndInvokeMethod() { assertThat(writerVisitor.write()).isEqualTo("super.name = super.getName()"); } + @Test + public void writeUnaryOperationExpr_postfixIncrement() { + VariableExpr variableExpr = + VariableExpr.withVariable(Variable.builder().setType(TypeNode.INT).setName("i").build()); + UnaryOperationExpr postIncrementOperationExpr = + UnaryOperationExpr.postfixIncrementWithExpr(variableExpr); + postIncrementOperationExpr.accept(writerVisitor); + assertThat(writerVisitor.write()).isEqualTo("i++"); + } + + @Test + public void writeUnaryOperationExpr_logicalNot() { + MethodInvocationExpr methodInvocationExpr = + MethodInvocationExpr.builder() + .setMethodName("isEmpty") + .setReturnType(TypeNode.BOOLEAN) + .build(); + UnaryOperationExpr logicalNotOperationExpr = + UnaryOperationExpr.logicalNotWithExpr(methodInvocationExpr); + logicalNotOperationExpr.accept(writerVisitor); + assertThat(writerVisitor.write()).isEqualTo("!isEmpty()"); + } + private static String createLines(int numLines) { return new String(new char[numLines]).replace("\0", "%s"); }