Add COMMENT ExpressionType

The comment expression allows the user to insert single-line
comments into the generated code. These will not be moved or
optimized away.

Comment expressions should make it easier to understand the
generated autodiff code.

Change-Id: Ie87d0698aa6af2aac8a437a13b25e0fef33dbfdc
diff --git a/include/ceres/codegen/internal/expression.h b/include/ceres/codegen/internal/expression.h
index 7de7a77..5e1ff2c 100644
--- a/include/ceres/codegen/internal/expression.h
+++ b/include/ceres/codegen/internal/expression.h
@@ -229,6 +229,10 @@
   ELSE,
   ENDIF,
 
+  // A single comment line. Even though comments are 'unused' expression they
+  // will not be optimized away.
+  COMMENT,
+
   // No Operation. A placeholder for an 'empty' expressions which will be
   // optimized out during code generation.
   NOP
@@ -301,6 +305,7 @@
   static Expression CreateIf(ExpressionId condition);
   static Expression CreateElse();
   static Expression CreateEndIf();
+  static Expression CreateComment(const std::string& comment);
 
   // Returns true if this is an arithmetic expression.
   // Arithmetic expressions must have a valid left hand side.
diff --git a/include/ceres/codegen/macros.h b/include/ceres/codegen/macros.h
index fbb2951..ee5068a 100644
--- a/include/ceres/codegen/macros.h
+++ b/include/ceres/codegen/macros.h
@@ -104,6 +104,7 @@
 #define CERES_IF(condition_) if (condition_)
 #define CERES_ELSE else
 #define CERES_ENDIF
+#define CERES_COMMENT(comment_)
 #else
 #define CERES_LOCAL_VARIABLE(_template_type, _local_variable)            \
   ceres::internal::InputAssignment<_template_type>::Get(_local_variable, \
@@ -114,6 +115,8 @@
   AddExpressionToGraph(ceres::internal::Expression::CreateElse());
 #define CERES_ENDIF \
   AddExpressionToGraph(ceres::internal::Expression::CreateEndIf());
+#define CERES_COMMENT(comment_) \
+  AddExpressionToGraph(ceres::internal::Expression::CreateComment(comment_))
 #endif
 
 namespace ceres {
diff --git a/internal/ceres/code_generator.cc b/internal/ceres/code_generator.cc
index dd0ab82..76ab48f 100644
--- a/internal/ceres/code_generator.cc
+++ b/internal/ceres/code_generator.cc
@@ -221,6 +221,14 @@
       result << indentation_ << "}";
       break;
     }
+    case ExpressionType::COMMENT: {
+      //
+      // Format:     // <name>
+      // Example:    // this is a comment
+      //
+      result << indentation_ << "// " + name;
+      break;
+    }
     case ExpressionType::NOP: {
       //
       // Format:     // <NOP>
diff --git a/internal/ceres/codegen/code_generator_test.cc b/internal/ceres/codegen/code_generator_test.cc
index a73ccac..17246a8 100644
--- a/internal/ceres/codegen/code_generator_test.cc
+++ b/internal/ceres/codegen/code_generator_test.cc
@@ -510,5 +510,13 @@
   GenerateAndCheck(graph, expected_code);
 }
 
+TEST(CodeGenerator, COMMENT) {
+  StartRecordingExpressions();
+  CERES_COMMENT("Hello");
+  auto graph = StopRecordingExpressions();
+  std::vector<std::string> expected_code = {"{", "  // Hello", "}"};
+  GenerateAndCheck(graph, expected_code);
+}
+
 }  // namespace internal
 }  // namespace ceres
diff --git a/internal/ceres/codegen/expression_ref_test.cc b/internal/ceres/codegen/expression_ref_test.cc
index 88d0562..30672ad 100644
--- a/internal/ceres/codegen/expression_ref_test.cc
+++ b/internal/ceres/codegen/expression_ref_test.cc
@@ -401,5 +401,15 @@
   EXPECT_EQ(reference, graph);
 }
 
+TEST(ExpressionRef, COMMENT) {
+  StartRecordingExpressions();
+  CERES_COMMENT("This is a comment");
+  auto graph = StopRecordingExpressions();
+
+  ExpressionGraph reference;
+  reference.InsertBack(Expression::CreateComment("This is a comment"));
+  EXPECT_EQ(reference, graph);
+}
+
 }  // namespace internal
 }  // namespace ceres
diff --git a/internal/ceres/codegen/expression_test.cc b/internal/ceres/codegen/expression_test.cc
index ae96ea0..65daf69 100644
--- a/internal/ceres/codegen/expression_test.cc
+++ b/internal/ceres/codegen/expression_test.cc
@@ -169,6 +169,14 @@
                        {},
                        "",
                        0));
+
+  EXPECT_EQ(Expression::CreateComment("Test"),
+            Expression(ExpressionType::COMMENT,
+                       ExpressionReturnType::VOID,
+                       kInvalidExpressionId,
+                       {},
+                       "Test",
+                       0));
 }
 
 TEST(Expression, IsArithmeticExpression) {
@@ -194,6 +202,7 @@
       Expression::CreateBinaryCompare("<", 3, 5).IsControlExpression());
   ASSERT_TRUE(Expression::CreateIf(5).IsControlExpression());
   ASSERT_TRUE(Expression::CreateEndIf().IsControlExpression());
+  ASSERT_TRUE(Expression::CreateComment("Test").IsControlExpression());
   ASSERT_TRUE(Expression().IsControlExpression());
 }
 
diff --git a/internal/ceres/expression.cc b/internal/ceres/expression.cc
index 0fec5a0..1a475b3 100644
--- a/internal/ceres/expression.cc
+++ b/internal/ceres/expression.cc
@@ -160,13 +160,22 @@
   return Expression(ExpressionType::ENDIF);
 }
 
+Expression Expression::CreateComment(const std::string& comment) {
+  return Expression(ExpressionType::COMMENT,
+                    ExpressionReturnType::VOID,
+                    kInvalidExpressionId,
+                    {},
+                    comment);
+}
+
 bool Expression::IsArithmeticExpression() const {
   return !IsControlExpression();
 }
 
 bool Expression::IsControlExpression() const {
   return type_ == ExpressionType::IF || type_ == ExpressionType::ELSE ||
-         type_ == ExpressionType::ENDIF || type_ == ExpressionType::NOP;
+         type_ == ExpressionType::ENDIF || type_ == ExpressionType::NOP ||
+         type_ == ExpressionType::COMMENT;
 }
 
 bool Expression::IsReplaceableBy(const Expression& other) const {