Merge pull request #586 from LefterisJP/natspec_contract_tags

Natspec title and author tag.
This commit is contained in:
chriseth 2014-12-10 19:23:57 +01:00
commit c7c189cac0
4 changed files with 116 additions and 31 deletions

9
AST.h
View File

@ -156,13 +156,15 @@ class ContractDefinition: public Declaration
public: public:
ContractDefinition(Location const& _location, ContractDefinition(Location const& _location,
ASTPointer<ASTString> const& _name, ASTPointer<ASTString> const& _name,
ASTPointer<ASTString> const& _documentation,
std::vector<ASTPointer<StructDefinition>> const& _definedStructs, std::vector<ASTPointer<StructDefinition>> const& _definedStructs,
std::vector<ASTPointer<VariableDeclaration>> const& _stateVariables, std::vector<ASTPointer<VariableDeclaration>> const& _stateVariables,
std::vector<ASTPointer<FunctionDefinition>> const& _definedFunctions): std::vector<ASTPointer<FunctionDefinition>> const& _definedFunctions):
Declaration(_location, _name), Declaration(_location, _name),
m_definedStructs(_definedStructs), m_definedStructs(_definedStructs),
m_stateVariables(_stateVariables), m_stateVariables(_stateVariables),
m_definedFunctions(_definedFunctions) m_definedFunctions(_definedFunctions),
m_documentation(_documentation)
{} {}
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
@ -172,6 +174,10 @@ public:
std::vector<ASTPointer<VariableDeclaration>> const& getStateVariables() const { return m_stateVariables; } std::vector<ASTPointer<VariableDeclaration>> const& getStateVariables() const { return m_stateVariables; }
std::vector<ASTPointer<FunctionDefinition>> const& getDefinedFunctions() const { return m_definedFunctions; } std::vector<ASTPointer<FunctionDefinition>> const& getDefinedFunctions() const { return m_definedFunctions; }
/// @return A shared pointer of an ASTString.
/// Can contain a nullptr in which case indicates absence of documentation
ASTPointer<ASTString> const& getDocumentation() const { return m_documentation; }
/// Returns the functions that make up the calling interface in the intended order. /// Returns the functions that make up the calling interface in the intended order.
std::vector<FunctionDefinition const*> getInterfaceFunctions() const; std::vector<FunctionDefinition const*> getInterfaceFunctions() const;
@ -179,6 +185,7 @@ private:
std::vector<ASTPointer<StructDefinition>> m_definedStructs; std::vector<ASTPointer<StructDefinition>> m_definedStructs;
std::vector<ASTPointer<VariableDeclaration>> m_stateVariables; std::vector<ASTPointer<VariableDeclaration>> m_stateVariables;
std::vector<ASTPointer<FunctionDefinition>> m_definedFunctions; std::vector<ASTPointer<FunctionDefinition>> m_definedFunctions;
ASTPointer<ASTString> m_documentation;
}; };
class StructDefinition: public Declaration class StructDefinition: public Declaration

View File

@ -75,7 +75,7 @@ std::unique_ptr<std::string> InterfaceHandler::getUserDocumentation(ContractDefi
if (strPtr) if (strPtr)
{ {
resetUser(); resetUser();
parseDocString(*strPtr); parseDocString(*strPtr, CommentOwner::FUNCTION);
if (!m_notice.empty()) if (!m_notice.empty())
{// since @notice is the only user tag if missing function should not appear {// since @notice is the only user tag if missing function should not appear
user["notice"] = Json::Value(m_notice); user["notice"] = Json::Value(m_notice);
@ -95,6 +95,20 @@ std::unique_ptr<std::string> InterfaceHandler::getDevDocumentation(ContractDefin
Json::Value doc; Json::Value doc;
Json::Value methods(Json::objectValue); Json::Value methods(Json::objectValue);
auto contractDoc = _contractDef.getDocumentation();
if (contractDoc)
{
m_contractAuthor.clear();
m_title.clear();
parseDocString(*contractDoc, CommentOwner::CONTRACT);
if (!m_contractAuthor.empty())
doc["author"] = m_contractAuthor;
if (!m_title.empty())
doc["title"] = m_title;
}
for (FunctionDefinition const* f: _contractDef.getInterfaceFunctions()) for (FunctionDefinition const* f: _contractDef.getInterfaceFunctions())
{ {
Json::Value method; Json::Value method;
@ -102,16 +116,21 @@ std::unique_ptr<std::string> InterfaceHandler::getDevDocumentation(ContractDefin
if (strPtr) if (strPtr)
{ {
resetDev(); resetDev();
parseDocString(*strPtr); parseDocString(*strPtr, CommentOwner::FUNCTION);
if (!m_dev.empty()) if (!m_dev.empty())
method["details"] = Json::Value(m_dev); method["details"] = Json::Value(m_dev);
if (!m_author.empty())
method["author"] = m_author;
Json::Value params(Json::objectValue); Json::Value params(Json::objectValue);
for (auto const& pair: m_params) for (auto const& pair: m_params)
params[pair.first] = pair.second; params[pair.first] = pair.second;
if (!m_params.empty()) if (!m_params.empty())
method["params"] = params; method["params"] = params;
if (!m_return.empty()) if (!m_return.empty())
method["return"] = m_return; method["return"] = m_return;
@ -133,6 +152,7 @@ void InterfaceHandler::resetUser()
void InterfaceHandler::resetDev() void InterfaceHandler::resetDev()
{ {
m_dev.clear(); m_dev.clear();
m_author.clear();
m_return.clear(); m_return.clear();
m_params.clear(); m_params.clear();
} }
@ -193,10 +213,12 @@ std::string::const_iterator InterfaceHandler::appendDocTagParam(std::string::con
std::string::const_iterator InterfaceHandler::parseDocTag(std::string::const_iterator _pos, std::string::const_iterator InterfaceHandler::parseDocTag(std::string::const_iterator _pos,
std::string::const_iterator _end, std::string::const_iterator _end,
std::string const& _tag) std::string const& _tag,
CommentOwner _owner)
{ {
// LTODO: need to check for @(start of a tag) between here and the end of line // LTODO: need to check for @(start of a tag) between here and the end of line
// for all cases // for all cases. Also somehow automate list of acceptable tags for each
// language construct since current way does not scale well.
if (m_lastTag == DocTagType::NONE || _tag != "") if (m_lastTag == DocTagType::NONE || _tag != "")
{ {
if (_tag == "dev") if (_tag == "dev")
@ -205,37 +227,77 @@ std::string::const_iterator InterfaceHandler::parseDocTag(std::string::const_ite
return parseDocTagLine(_pos, _end, m_notice, DocTagType::NOTICE); return parseDocTagLine(_pos, _end, m_notice, DocTagType::NOTICE);
else if (_tag == "return") else if (_tag == "return")
return parseDocTagLine(_pos, _end, m_return, DocTagType::RETURN); return parseDocTagLine(_pos, _end, m_return, DocTagType::RETURN);
else if (_tag == "author")
{
if (_owner == CommentOwner::CONTRACT)
return parseDocTagLine(_pos, _end, m_contractAuthor, DocTagType::AUTHOR);
else if (_owner == CommentOwner::FUNCTION)
return parseDocTagLine(_pos, _end, m_author, DocTagType::AUTHOR);
else
// LTODO: for now this else makes no sense but later comments will go to more language constructs
BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("@author tag is legal only for contracts"));
}
else if (_tag == "title")
{
if (_owner == CommentOwner::CONTRACT)
return parseDocTagLine(_pos, _end, m_title, DocTagType::TITLE);
else
// LTODO: Unknown tag, throw some form of warning and not just an exception
BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("@title tag is legal only for contracts"));
}
else if (_tag == "param") else if (_tag == "param")
return parseDocTagParam(_pos, _end); return parseDocTagParam(_pos, _end);
else else
{
// LTODO: Unknown tag, throw some form of warning and not just an exception // LTODO: Unknown tag, throw some form of warning and not just an exception
BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("Unknown tag " + _tag + " encountered")); BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("Unknown tag " + _tag + " encountered"));
}
} }
else else
return appendDocTag(_pos, _end); return appendDocTag(_pos, _end, _owner);
} }
std::string::const_iterator InterfaceHandler::appendDocTag(std::string::const_iterator _pos, std::string::const_iterator InterfaceHandler::appendDocTag(std::string::const_iterator _pos,
std::string::const_iterator _end) std::string::const_iterator _end,
CommentOwner _owner)
{ {
switch (m_lastTag) switch (m_lastTag)
{ {
case DocTagType::DEV: case DocTagType::DEV:
m_dev += " "; m_dev += " ";
return parseDocTagLine(_pos, _end, m_dev, DocTagType::DEV); return parseDocTagLine(_pos, _end, m_dev, DocTagType::DEV);
case DocTagType::NOTICE: case DocTagType::NOTICE:
m_notice += " "; m_notice += " ";
return parseDocTagLine(_pos, _end, m_notice, DocTagType::NOTICE); return parseDocTagLine(_pos, _end, m_notice, DocTagType::NOTICE);
case DocTagType::RETURN: case DocTagType::RETURN:
m_return += " "; m_return += " ";
return parseDocTagLine(_pos, _end, m_return, DocTagType::RETURN); return parseDocTagLine(_pos, _end, m_return, DocTagType::RETURN);
case DocTagType::PARAM: case DocTagType::AUTHOR:
return appendDocTagParam(_pos, _end); if (_owner == CommentOwner::CONTRACT)
default: {
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Internal: Illegal documentation tag type")); m_contractAuthor += " ";
break; return parseDocTagLine(_pos, _end, m_contractAuthor, DocTagType::AUTHOR);
}
else if (_owner == CommentOwner::FUNCTION)
{
m_author += " ";
return parseDocTagLine(_pos, _end, m_author, DocTagType::AUTHOR);
}
else
// LTODO: Unknown tag, throw some form of warning and not just an exception
BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("@author tag in illegal comment"));
case DocTagType::TITLE:
if (_owner == CommentOwner::CONTRACT)
{
m_title += " ";
return parseDocTagLine(_pos, _end, m_title, DocTagType::TITLE);
}
else
// LTODO: Unknown tag, throw some form of warning and not just an exception
BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("@title tag in illegal comment"));
case DocTagType::PARAM:
return appendDocTagParam(_pos, _end);
default:
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Internal: Illegal documentation tag type"));
break;
} }
} }
@ -247,7 +309,7 @@ static inline std::string::const_iterator getFirstSpaceOrNl(std::string::const_i
return (spacePos < nlPos) ? spacePos : nlPos; return (spacePos < nlPos) ? spacePos : nlPos;
} }
void InterfaceHandler::parseDocString(std::string const& _string) void InterfaceHandler::parseDocString(std::string const& _string, CommentOwner _owner)
{ {
auto currPos = _string.begin(); auto currPos = _string.begin();
auto end = _string.end(); auto end = _string.end();
@ -265,10 +327,10 @@ void InterfaceHandler::parseDocString(std::string const& _string)
BOOST_THROW_EXCEPTION(DocstringParsingError() << BOOST_THROW_EXCEPTION(DocstringParsingError() <<
errinfo_comment("End of tag " + std::string(tagPos, tagNameEndPos) + "not found")); errinfo_comment("End of tag " + std::string(tagPos, tagNameEndPos) + "not found"));
currPos = parseDocTag(tagNameEndPos + 1, end, std::string(tagPos + 1, tagNameEndPos)); currPos = parseDocTag(tagNameEndPos + 1, end, std::string(tagPos + 1, tagNameEndPos), _owner);
} }
else if (m_lastTag != DocTagType::NONE) // continuation of the previous tag else if (m_lastTag != DocTagType::NONE) // continuation of the previous tag
currPos = appendDocTag(currPos + 1, end); currPos = appendDocTag(currPos + 1, end, _owner);
else if (currPos != end) // skip the line if a newline was found else if (currPos != end) // skip the line if a newline was found
currPos = nlPos + 1; currPos = nlPos + 1;
} }

View File

@ -45,7 +45,15 @@ enum class DocTagType: uint8_t
DEV, DEV,
NOTICE, NOTICE,
PARAM, PARAM,
RETURN RETURN,
AUTHOR,
TITLE
};
enum class CommentOwner
{
CONTRACT,
FUNCTION
}; };
class InterfaceHandler class InterfaceHandler
@ -89,12 +97,14 @@ private:
std::string::const_iterator _end); std::string::const_iterator _end);
std::string::const_iterator appendDocTagParam(std::string::const_iterator _pos, std::string::const_iterator appendDocTagParam(std::string::const_iterator _pos,
std::string::const_iterator _end); std::string::const_iterator _end);
void parseDocString(std::string const& _string); void parseDocString(std::string const& _string, CommentOwner _owner);
std::string::const_iterator appendDocTag(std::string::const_iterator _pos, std::string::const_iterator appendDocTag(std::string::const_iterator _pos,
std::string::const_iterator _end); std::string::const_iterator _end,
CommentOwner _owner);
std::string::const_iterator parseDocTag(std::string::const_iterator _pos, std::string::const_iterator parseDocTag(std::string::const_iterator _pos,
std::string::const_iterator _end, std::string::const_iterator _end,
std::string const& _tag); std::string const& _tag,
CommentOwner _owner);
Json::StyledWriter m_writer; Json::StyledWriter m_writer;
@ -103,6 +113,9 @@ private:
std::string m_notice; std::string m_notice;
std::string m_dev; std::string m_dev;
std::string m_return; std::string m_return;
std::string m_contractAuthor;
std::string m_author;
std::string m_title;
std::vector<std::pair<std::string, std::string>> m_params; std::vector<std::pair<std::string, std::string>> m_params;
}; };

View File

@ -112,6 +112,9 @@ ASTPointer<ImportDirective> Parser::parseImportDirective()
ASTPointer<ContractDefinition> Parser::parseContractDefinition() ASTPointer<ContractDefinition> Parser::parseContractDefinition()
{ {
ASTNodeFactory nodeFactory(*this); ASTNodeFactory nodeFactory(*this);
ASTPointer<ASTString> docstring;
if (m_scanner->getCurrentCommentLiteral() != "")
docstring = make_shared<ASTString>(m_scanner->getCurrentCommentLiteral());
expectToken(Token::CONTRACT); expectToken(Token::CONTRACT);
ASTPointer<ASTString> name = expectIdentifierToken(); ASTPointer<ASTString> name = expectIdentifierToken();
expectToken(Token::LBRACE); expectToken(Token::LBRACE);
@ -146,7 +149,7 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition()
} }
nodeFactory.markEndPosition(); nodeFactory.markEndPosition();
expectToken(Token::RBRACE); expectToken(Token::RBRACE);
return nodeFactory.createNode<ContractDefinition>(name, structs, stateVariables, functions); return nodeFactory.createNode<ContractDefinition>(name, docstring, structs, stateVariables, functions);
} }
ASTPointer<FunctionDefinition> Parser::parseFunctionDefinition(bool _isPublic) ASTPointer<FunctionDefinition> Parser::parseFunctionDefinition(bool _isPublic)