hidl-gen: understand doc comments
Doc comments were used by hidl-doc in order to
generate documentation. However, because hidl-doc
tokenizes the entire file including the comments
some of the information is lost. Rather than refactor
hidl-doc, we are going to use 3rd party doc generation
tools in order to generate docs from the output of
hidl-gen.
This has a couple of benefits:
- people automatically get the documentation if they are using
an IDE which pulls the documentation
- hidl-doc/hidl-gen won't get out of sync
- documentation will be closer to actual usage
This will require ABI-safe changes to some HAL interfaces.
Bug: 78135149
Test: manually inspect hidl-gen output.
Test: (sanity) run_all_host_tests.sh
Test: (sanity) run_all_device_tests.sh
Change-Id: I9a09ed48e2e3834fab7e032e29c48f466510e51f
diff --git a/Android.bp b/Android.bp
index d1e9c5a..dd51705 100644
--- a/Android.bp
+++ b/Android.bp
@@ -78,6 +78,7 @@
"CompoundType.cpp",
"ConstantExpression.cpp",
"DeathRecipientType.cpp",
+ "DocComment.cpp",
"EnumType.cpp",
"HandleType.cpp",
"HidlTypeAssertion.cpp",
diff --git a/CompoundType.cpp b/CompoundType.cpp
index fc3c27d..87f0f14 100644
--- a/CompoundType.cpp
+++ b/CompoundType.cpp
@@ -386,6 +386,7 @@
if (containsPointer()) {
for (const auto &field : *mFields) {
+ field->emitDocComment(out);
out << field->type().getCppStackType()
<< " "
<< field->name()
@@ -577,7 +578,9 @@
Scope::emitJavaTypeDeclarations(out, false /* atTopLevel */);
- for (const auto &field : *mFields) {
+ for (const auto& field : *mFields) {
+ field->emitDocComment(out);
+
out << "public ";
field->type().emitJavaFieldInitializer(out, field->name());
diff --git a/DocComment.cpp b/DocComment.cpp
new file mode 100644
index 0000000..91bc676
--- /dev/null
+++ b/DocComment.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "DocComment.h"
+
+#include <hidl-util/StringHelper.h>
+
+#include <cctype>
+#include <sstream>
+
+namespace android {
+
+DocComment::DocComment(const std::string& comment) {
+ std::vector<std::string> lines;
+ StringHelper::SplitString(comment, '\n', &lines);
+
+ bool foundFirstLine = false;
+
+ std::ostringstream is;
+ for (size_t l = 0; l < lines.size(); l++) {
+ const std::string& line = lines[l];
+
+ // Delete prefixes like " * ", " *", or " ".
+ size_t idx = 0;
+ for (; idx < line.size() && isspace(line[idx]); idx++)
+ ;
+ if (idx < line.size() && line[idx] == '*') idx++;
+ if (idx < line.size() && line[idx] == ' ') idx++;
+
+ if (idx < line.size()) {
+ foundFirstLine = true;
+ }
+
+ if (!foundFirstLine) continue;
+
+ is << line.substr(idx);
+
+ if (l + 1 < lines.size()) {
+ is << "\n";
+ }
+ }
+
+ mComment = is.str();
+}
+
+void DocComment::merge(const DocComment* comment) {
+ mComment = mComment + "\n\n" + comment->mComment;
+}
+
+void DocComment::emit(Formatter& out) const {
+ out << "/**\n";
+ out.setLinePrefix(" * ");
+ out << mComment;
+ out.unsetLinePrefix();
+ out << " */\n";
+}
+
+} // namespace android
diff --git a/DocComment.h b/DocComment.h
new file mode 100644
index 0000000..e0677eb
--- /dev/null
+++ b/DocComment.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef DOC_COMMENT_H_
+
+#define DOC_COMMENT_H_
+
+#include <hidl-util/Formatter.h>
+
+#include <string>
+
+namespace android {
+
+struct DocComment {
+ DocComment(const std::string& comment);
+
+ void merge(const DocComment* comment);
+
+ void emit(Formatter& out) const;
+
+ private:
+ std::string mComment;
+};
+
+struct DocCommentable {
+ void setDocComment(const DocComment* docComment) { mDocComment = docComment; }
+ void emitDocComment(Formatter& out) const {
+ if (mDocComment != nullptr) {
+ mDocComment->emit(out);
+ }
+ }
+
+ private:
+ const DocComment* mDocComment = nullptr;
+};
+
+} // namespace android
+
+#endif // DOC_COMMENT_H_
diff --git a/EnumType.cpp b/EnumType.cpp
index 1984693..ead64ba 100644
--- a/EnumType.cpp
+++ b/EnumType.cpp
@@ -254,6 +254,8 @@
const auto &type = *it;
for (const auto &entry : type->values()) {
+ entry->emitDocComment(out);
+
out << entry->name();
std::string value = entry->cppValue(scalarType->getKind());
@@ -472,6 +474,8 @@
const auto &type = *it;
for (const auto &entry : type->values()) {
+ entry->emitDocComment(out);
+
out << "public static final "
<< typeName
<< " "
diff --git a/EnumType.h b/EnumType.h
index d219506..eef4eb2 100644
--- a/EnumType.h
+++ b/EnumType.h
@@ -137,7 +137,7 @@
DISALLOW_COPY_AND_ASSIGN(EnumType);
};
-struct EnumValue : public LocalIdentifier {
+struct EnumValue : public LocalIdentifier, DocCommentable {
EnumValue(const char* name, ConstantExpression* value, const Location& location);
std::string name() const;
diff --git a/Method.h b/Method.h
index 291ea34..711f087 100644
--- a/Method.h
+++ b/Method.h
@@ -28,6 +28,7 @@
#include <unordered_set>
#include <vector>
+#include "DocComment.h"
#include "Location.h"
#include "Reference.h"
@@ -50,7 +51,7 @@
using MethodImpl = std::map<MethodImplType, std::function<void(Formatter &)>>;
-struct Method {
+struct Method : DocCommentable {
Method(const char* name, std::vector<NamedReference<Type>*>* args,
std::vector<NamedReference<Type>*>* results, bool oneway,
std::vector<Annotation*>* annotations, const Location& location);
diff --git a/Reference.h b/Reference.h
index 4b03c15..7ddc889 100644
--- a/Reference.h
+++ b/Reference.h
@@ -21,6 +21,7 @@
#include <android-base/logging.h>
#include <hidl-util/FQName.h>
+#include "DocComment.h"
#include "Location.h"
namespace android {
@@ -120,7 +121,7 @@
};
template <class T>
-struct NamedReference : public Reference<T> {
+struct NamedReference : public Reference<T>, DocCommentable {
NamedReference(const std::string& name, const Reference<T>& reference, const Location& location)
: Reference<T>(reference, location), mName(name) {}
diff --git a/Scope.cpp b/Scope.cpp
index f2cd80c..bff097f 100644
--- a/Scope.cpp
+++ b/Scope.cpp
@@ -160,6 +160,7 @@
}
for (const Type* type : mTypes) {
+ type->emitDocComment(out);
type->emitTypeDeclarations(out);
}
}
@@ -188,6 +189,7 @@
}
for (const Type* type : mTypes) {
+ type->emitDocComment(out);
type->emitJavaTypeDeclarations(out, atTopLevel);
}
}
diff --git a/Type.h b/Type.h
index b1b97f4..a0ed5bc 100644
--- a/Type.h
+++ b/Type.h
@@ -26,6 +26,7 @@
#include <unordered_set>
#include <vector>
+#include "DocComment.h"
#include "Reference.h"
namespace android {
@@ -36,7 +37,7 @@
struct ScalarType;
struct Scope;
-struct Type {
+struct Type : DocCommentable {
Type(Scope* parent);
virtual ~Type();
diff --git a/c2hal/test/simple.h b/c2hal/test/simple.h
index e3d7f3e..701ebe4 100644
--- a/c2hal/test/simple.h
+++ b/c2hal/test/simple.h
@@ -32,7 +32,7 @@
/* Simple example */
typedef struct simple_t {
- /**
+ /*
* Common methods of the simple device.
*/
struct hw_device_t common;
@@ -67,7 +67,7 @@
} simple_location_t;
-/** convenience API for coloring */
+/* convenience API for coloring */
static inline int showColor(const struct hw_module_t* module,
struct simple_t** device) {
diff --git a/c2hal/test/test.h b/c2hal/test/test.h
index b1a0058..3a83b0c 100644
--- a/c2hal/test/test.h
+++ b/c2hal/test/test.h
@@ -34,7 +34,6 @@
get rid of magic numbers */
/* test */
-/** test */
/* test **/
/* test / ** ** / test */
/* test //// ***** test /****/
@@ -91,7 +90,6 @@
#undef ONE
-/*****************************************************************************/
typedef void (*no_arg_fun)(void);
typedef int (*other_fun)(int j);
@@ -110,7 +108,7 @@
int (*global_fun_2)(struct framebuffer_device_t* dev, int enable);
typedef struct framebuffer_device_t {
- /**
+ /*
* Common methods of the framebuffer device.
*/
struct hw_device_t common;
@@ -324,7 +322,7 @@
float c;
} that_t;
-/**
+/*
* return the frame size (number of bytes per sample) of an output stream.
*/
static inline size_t audio_stream_out_frame_size(const struct audio_stream_out *s)
diff --git a/generateCpp.cpp b/generateCpp.cpp
index 93bfef4..4fd3956 100644
--- a/generateCpp.cpp
+++ b/generateCpp.cpp
@@ -288,6 +288,8 @@
method->dumpAnnotations(out);
+ method->emitDocComment(out);
+
if (elidedReturn) {
out << "virtual ::android::hardware::Return<";
out << elidedReturn->type().getCppResultType() << "> ";
diff --git a/generateJava.cpp b/generateJava.cpp
index 531211d..bdbe25a 100644
--- a/generateJava.cpp
+++ b/generateJava.cpp
@@ -237,6 +237,8 @@
out << "}\n\n";
}
+ method->emitDocComment(out);
+
if (returnsValue && !needsCallback) {
out << method->results()[0]->type().getJavaType();
} else {
diff --git a/hidl-gen_l.ll b/hidl-gen_l.ll
index 40c03b2..c94df01 100644
--- a/hidl-gen_l.ll
+++ b/hidl-gen_l.ll
@@ -35,6 +35,7 @@
#include "CompoundType.h"
#include "ConstantExpression.h"
#include "DeathRecipientType.h"
+#include "DocComment.h"
#include "EnumType.h"
#include "HandleType.h"
#include "MemoryType.h"
@@ -54,6 +55,8 @@
using namespace android;
using token = yy::parser::token;
+static std::string gCurrentComment;
+
#define SCALAR_TYPE(kind) \
{ \
yylval->type = new ScalarType(ScalarType::kind, *scope); \
@@ -81,13 +84,24 @@
%option bison-locations
%x COMMENT_STATE
+%x DOC_COMMENT_STATE
%%
-"/*" { BEGIN(COMMENT_STATE); }
-<COMMENT_STATE>"*/" { BEGIN(INITIAL); }
-<COMMENT_STATE>[\n] { yylloc->lines(); }
-<COMMENT_STATE>. { }
+"/**" { gCurrentComment.clear(); BEGIN(DOC_COMMENT_STATE); }
+<DOC_COMMENT_STATE>"*/" {
+ BEGIN(INITIAL);
+ yylval->docComment = new DocComment(gCurrentComment);
+ return token::DOC_COMMENT;
+ }
+<DOC_COMMENT_STATE>[^*\n]* { gCurrentComment += yytext; }
+<DOC_COMMENT_STATE>[\n] { gCurrentComment += yytext; yylloc->lines(); }
+<DOC_COMMENT_STATE>[*] { gCurrentComment += yytext; }
+
+"/*" { BEGIN(COMMENT_STATE); }
+<COMMENT_STATE>"*/" { BEGIN(INITIAL); }
+<COMMENT_STATE>[\n] { yylloc->lines(); }
+<COMMENT_STATE>. { }
"//"[^\r\n]* { /* skip C++ style comment */ }
diff --git a/hidl-gen_y.yy b/hidl-gen_y.yy
index 005aa59..da45823 100644
--- a/hidl-gen_y.yy
+++ b/hidl-gen_y.yy
@@ -21,6 +21,7 @@
#include "ArrayType.h"
#include "CompoundType.h"
#include "ConstantExpression.h"
+#include "DocComment.h"
#include "EnumType.h"
#include "Interface.h"
#include "Location.h"
@@ -214,6 +215,10 @@
%expect-rr 0
%error-verbose
+%debug
+
+%token<docComment> DOC_COMMENT "doc comment"
+
%token<void> ENUM "keyword `enum`"
%token<void> EXTENDS "keyword `extends`"
%token<str> FQNAME "fully-qualified name"
@@ -257,6 +262,8 @@
/* Precedence level 3, RTL; but we have to use %left here */
%left UNARY_MINUS UNARY_PLUS '!' '~'
+%type<docComment> doc_comments
+
%type<str> error_stmt error
%type<str> package
%type<fqName> fqname
@@ -267,18 +274,19 @@
%type<referenceToType> array_type_base
%type<arrayType> array_type
%type<referenceToType> opt_extends
-%type<type> type_declaration type_declaration_body interface_declaration typedef_declaration
+%type<type> type_declaration commentable_type_declaration type_declaration_body
+%type<type> interface_declaration typedef_declaration
%type<type> named_struct_or_union_declaration named_enum_declaration
%type<type> compound_declaration annotated_compound_declaration
-%type<field> field_declaration
+%type<field> field_declaration commentable_field_declaration
%type<fields> field_declarations struct_or_union_body
%type<constantExpression> const_expr
-%type<enumValue> enum_value
+%type<enumValue> enum_value commentable_enum_value
%type<enumValues> enum_values enum_declaration_body
%type<typedVars> typed_vars
%type<typedVar> typed_var
-%type<method> method_declaration
+%type<method> method_declaration commentable_method_declaration
%type<compoundStyle> struct_or_union_keyword
%type<stringVec> annotation_string_values annotation_string_value
%type<constExprVec> annotation_const_expr_values annotation_const_expr_value
@@ -312,14 +320,24 @@
android::AnnotationParamVector *annotationParams;
android::Annotation *annotation;
std::vector<android::Annotation *> *annotations;
+ android::DocComment* docComment;
}
%%
program
- : package
- imports
- type_declarations
+ // Don't care if license header is a doc comment or not
+ : DOC_COMMENT package imports type_declarations
+ | package imports type_declarations
+ ;
+
+doc_comments
+ : DOC_COMMENT { $$ = $1; }
+ | doc_comments DOC_COMMENT
+ {
+ $1->merge($2);
+ $$ = $1;
+ }
;
valid_identifier
@@ -549,7 +567,7 @@
interface_declarations
: /* empty */
- | interface_declarations type_declaration
+ | interface_declarations commentable_type_declaration
{
CHECK((*scope)->isInterface());
@@ -562,7 +580,7 @@
YYERROR;
}
}
- | interface_declarations method_declaration
+ | interface_declarations commentable_method_declaration
{
CHECK((*scope)->isInterface());
@@ -590,7 +608,16 @@
type_declarations
: /* empty */
| error_stmt
- | type_declarations type_declaration
+ | type_declarations commentable_type_declaration
+ ;
+
+commentable_type_declaration
+ : doc_comments type_declaration
+ {
+ $2->setDocComment($1);
+ $$ = $2;
+ }
+ | type_declaration { $$ = $1; }
;
type_declaration
@@ -750,6 +777,17 @@
}
;
+commentable_method_declaration
+ : doc_comments method_declaration
+ {
+ if ($2 != nullptr) $2->setDocComment($1);
+ $$ = $2;
+ }
+ | method_declaration
+ {
+ $$ = $1;
+ }
+
method_declaration
: error_stmt { $$ = nullptr; }
| opt_annotations valid_identifier '(' typed_vars ')' require_semicolon
@@ -860,7 +898,7 @@
field_declarations
: /* empty */ { $$ = new std::vector<NamedReference<Type>*>; }
- | field_declarations field_declaration
+ | field_declarations commentable_field_declaration
{
$$ = $1;
@@ -871,6 +909,14 @@
}
;
+commentable_field_declaration
+ : doc_comments field_declaration
+ {
+ if ($2 != nullptr) $2->setDocComment($1);
+ $$ = $2;
+ }
+ | field_declaration { $$ = $1; }
+
field_declaration
: error_stmt { $$ = nullptr; }
| type_or_inplace_compound_declaration valid_identifier require_semicolon
@@ -959,6 +1005,15 @@
: '{' enum_values opt_comma '}' { $$ = $2; }
;
+commentable_enum_value
+ : doc_comments enum_value
+ {
+ $2->setDocComment($1);
+ $$ = $2;
+ }
+ | enum_value { $$ = $1; }
+ ;
+
enum_value
: valid_identifier
{
@@ -973,24 +1028,24 @@
enum_values
: /* empty */
{ /* do nothing */ }
- | enum_value
+ | commentable_enum_value
{
CHECK((*scope)->isEnum());
static_cast<EnumType *>(*scope)->addValue($1);
}
- | enum_values ',' enum_value
+ | enum_values ',' commentable_enum_value
{
CHECK((*scope)->isEnum());
static_cast<EnumType *>(*scope)->addValue($3);
}
- | error ',' enum_value
+ | error ',' commentable_enum_value
{
ast->addSyntaxError();
CHECK((*scope)->isEnum());
static_cast<EnumType *>(*scope)->addValue($3);
}
- | enum_values ',' error ',' enum_value
+ | enum_values ',' error ',' commentable_enum_value
{
ast->addSyntaxError();
diff --git a/utils/Formatter.cpp b/utils/Formatter.cpp
index b5876dc..338cb17 100644
--- a/utils/Formatter.cpp
+++ b/utils/Formatter.cpp
@@ -123,8 +123,8 @@
if (pos == std::string::npos) {
if (mAtStartOfLine) {
- fprintf(mFile, "%s", mLinePrefix.c_str());
fprintf(mFile, "%*s", (int)(mSpacesPerIndent * mIndentDepth), "");
+ fprintf(mFile, "%s", mLinePrefix.c_str());
mAtStartOfLine = false;
}
@@ -132,17 +132,16 @@
break;
}
+ if (mAtStartOfLine && (pos > start || !mLinePrefix.empty())) {
+ fprintf(mFile, "%*s", (int)(mSpacesPerIndent * mIndentDepth), "");
+ fprintf(mFile, "%s", mLinePrefix.c_str());
+ }
+
if (pos == start) {
fprintf(mFile, "\n");
mAtStartOfLine = true;
} else if (pos > start) {
- if (mAtStartOfLine) {
- fprintf(mFile, "%s", mLinePrefix.c_str());
- fprintf(mFile, "%*s", (int)(mSpacesPerIndent * mIndentDepth), "");
- }
-
output(out.substr(start, pos - start + 1));
-
mAtStartOfLine = true;
}