diff options
author | chriseth <c@ethdev.com> | 2015-10-26 22:13:36 +0800 |
---|---|---|
committer | chriseth <c@ethdev.com> | 2015-10-26 22:24:36 +0800 |
commit | b4f561680a2a5169d1245271245e2b71822cb73a (patch) | |
tree | 6e3acecc9bbe825400e4297a32ff641df27d1943 /libsolidity | |
parent | d6e77ce0e1da577e5f2c000f89b4fba3505d84a0 (diff) | |
download | dexon-solidity-b4f561680a2a5169d1245271245e2b71822cb73a.tar.gz dexon-solidity-b4f561680a2a5169d1245271245e2b71822cb73a.tar.zst dexon-solidity-b4f561680a2a5169d1245271245e2b71822cb73a.zip |
Store docstrings in AST annotations.
Diffstat (limited to 'libsolidity')
-rw-r--r-- | libsolidity/analysis/DocStringAnalyser.cpp | 117 | ||||
-rw-r--r-- | libsolidity/analysis/DocStringAnalyser.h | 64 | ||||
-rw-r--r-- | libsolidity/ast/AST.cpp | 21 | ||||
-rw-r--r-- | libsolidity/ast/AST.h | 6 | ||||
-rw-r--r-- | libsolidity/ast/ASTAnnotations.h | 27 | ||||
-rw-r--r-- | libsolidity/interface/CompilerStack.cpp | 22 | ||||
-rw-r--r-- | libsolidity/interface/CompilerStack.h | 3 | ||||
-rw-r--r-- | libsolidity/interface/InterfaceHandler.cpp | 318 | ||||
-rw-r--r-- | libsolidity/interface/InterfaceHandler.h | 55 | ||||
-rw-r--r-- | libsolidity/parsing/DocStringParser.cpp | 141 | ||||
-rw-r--r-- | libsolidity/parsing/DocStringParser.h | 70 |
11 files changed, 509 insertions, 335 deletions
diff --git a/libsolidity/analysis/DocStringAnalyser.cpp b/libsolidity/analysis/DocStringAnalyser.cpp new file mode 100644 index 00000000..96cb5692 --- /dev/null +++ b/libsolidity/analysis/DocStringAnalyser.cpp @@ -0,0 +1,117 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2015 + * Parses and analyses the doc strings. + * Stores the parsing results in the AST annotations and reports errors. + */ + +#include <libsolidity/analysis/DocStringAnalyser.h> +#include <libsolidity/ast/AST.h> +#include <libsolidity/parsing/DocStringParser.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +bool DocStringAnalyser::analyseDocStrings(SourceUnit const& _sourceUnit) +{ + m_errorOccured = false; + _sourceUnit.accept(*this); + + return !m_errorOccured; +} + +bool DocStringAnalyser::visit(ContractDefinition const& _node) +{ + parseDocStrings(_node, _node.annotation()); + + static const set<string> validTags = set<string>{"author", "title", "dev", "notice"}; + for (auto const& docTag: _node.annotation().docTags) + if (!validTags.count(docTag.first)) + appendError("Doc tag @" + docTag.first + " not valid for contracts."); + + return true; +} + +bool DocStringAnalyser::visit(FunctionDefinition const& _node) +{ + handleCallable(_node, _node, _node.annotation()); + return true; +} + +bool DocStringAnalyser::visit(ModifierDefinition const& _node) +{ + handleCallable(_node, _node, _node.annotation()); + + return true; +} + +bool DocStringAnalyser::visit(EventDefinition const& _node) +{ + handleCallable(_node, _node, _node.annotation()); + + return true; +} + +void DocStringAnalyser::handleCallable( + CallableDeclaration const& _callable, + Documented const& _node, + DocumentedAnnotation& _annotation +) +{ + parseDocStrings(_node, _annotation); + static const set<string> validTags = set<string>{"author", "dev", "notice", "return", "param", "why3"}; + for (auto const& docTag: _annotation.docTags) + if (!validTags.count(docTag.first)) + appendError("Doc tag @" + docTag.first + " not valid for functions."); + + set<string> validParams; + for (auto const& p: _callable.parameters()) + validParams.insert(p->name()); + if (_callable.returnParameterList()) + for (auto const& p: _callable.returnParameterList()->parameters()) + validParams.insert(p->name()); + auto paramRange = _annotation.docTags.equal_range("param"); + for (auto i = paramRange.first; i != paramRange.second; ++i) + if (!validParams.count(i->second.paramName)) + appendError( + "Documented parameter \"" + + i->second.paramName + + "\" not found in the parameter list of the function." + ); +} + +void DocStringAnalyser::parseDocStrings(Documented const& _node, DocumentedAnnotation& _annotation) +{ + DocStringParser parser; + if (_node.documentation() && !_node.documentation()->empty()) + { + if (!parser.parse(*_node.documentation(), m_errors)) + m_errorOccured = true; + _annotation.docTags = parser.tags(); + } +} + +void DocStringAnalyser::appendError(string const& _description) +{ + auto err = make_shared<Error>(Error::Type::DocstringParsingError); + *err << errinfo_comment(_description); + m_errors.push_back(err); + m_errorOccured = true; +} diff --git a/libsolidity/analysis/DocStringAnalyser.h b/libsolidity/analysis/DocStringAnalyser.h new file mode 100644 index 00000000..06384c8d --- /dev/null +++ b/libsolidity/analysis/DocStringAnalyser.h @@ -0,0 +1,64 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2015 + * Parses and analyses the doc strings. + * Stores the parsing results in the AST annotations and reports errors. + */ + +#pragma once + +#include <libsolidity/ast/ASTVisitor.h> + +namespace dev +{ +namespace solidity +{ + +/** + * Parses and analyses the doc strings. + * Stores the parsing results in the AST annotations and reports errors. + */ +class DocStringAnalyser: private ASTConstVisitor +{ +public: + DocStringAnalyser(ErrorList& _errors): m_errors(_errors) {} + bool analyseDocStrings(SourceUnit const& _sourceUnit); + +private: + virtual bool visit(ContractDefinition const& _contract) override; + virtual bool visit(FunctionDefinition const& _function) override; + virtual bool visit(ModifierDefinition const& _modifier) override; + virtual bool visit(EventDefinition const& _event) override; + + void handleCallable( + CallableDeclaration const& _callable, + Documented const& _node, + DocumentedAnnotation& _annotation + ); + + void parseDocStrings(Documented const& _node, DocumentedAnnotation& _annotation); + + void appendError(std::string const& _description); + + bool m_errorOccured = false; + ErrorList& m_errors; +}; + +} +} diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 71d80a36..9d1fb811 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -248,16 +248,37 @@ string FunctionDefinition::externalSignature() const return FunctionType(*this).externalSignature(); } +FunctionDefinitionAnnotation& FunctionDefinition::annotation() const +{ + if (!m_annotation) + m_annotation = new FunctionDefinitionAnnotation(); + return static_cast<FunctionDefinitionAnnotation&>(*m_annotation); +} + TypePointer ModifierDefinition::type(ContractDefinition const*) const { return make_shared<ModifierType>(*this); } +ModifierDefinitionAnnotation& ModifierDefinition::annotation() const +{ + if (!m_annotation) + m_annotation = new ModifierDefinitionAnnotation(); + return static_cast<ModifierDefinitionAnnotation&>(*m_annotation); +} + TypePointer EventDefinition::type(ContractDefinition const*) const { return make_shared<FunctionType>(*this); } +EventDefinitionAnnotation& EventDefinition::annotation() const +{ + if (!m_annotation) + m_annotation = new EventDefinitionAnnotation(); + return static_cast<EventDefinitionAnnotation&>(*m_annotation); +} + UserDefinedTypeNameAnnotation& UserDefinedTypeName::annotation() const { if (!m_annotation) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 3fe447eb..6a593d3e 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -492,6 +492,8 @@ public: virtual TypePointer type(ContractDefinition const* m_currentContract) const override; + virtual FunctionDefinitionAnnotation& annotation() const override; + private: bool m_isConstructor; bool m_isDeclaredConst; @@ -593,6 +595,8 @@ public: virtual TypePointer type(ContractDefinition const* m_currentContract) const override; + virtual ModifierDefinitionAnnotation& annotation() const override; + private: ASTPointer<Block> m_body; }; @@ -647,6 +651,8 @@ public: virtual TypePointer type(ContractDefinition const* m_currentContract) const override; + virtual EventDefinitionAnnotation& annotation() const override; + private: bool m_anonymous = false; }; diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index d112b1ef..094a178e 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -41,13 +41,26 @@ struct ASTAnnotation virtual ~ASTAnnotation() {} }; +struct DocTag +{ + std::string content; ///< The text content of the tag. + std::string paramName; ///< Only used for @param, stores the parameter name. +}; + +struct DocumentedAnnotation +{ + virtual ~DocumentedAnnotation() {} + /// Mapping docstring tag name -> content. + std::multimap<std::string, DocTag> docTags; +}; + struct TypeDeclarationAnnotation: ASTAnnotation { /// The name of this type, prefixed by proper namespaces if globally accessible. std::string canonicalName; }; -struct ContractDefinitionAnnotation: TypeDeclarationAnnotation +struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, DocumentedAnnotation { /// Whether all functions are implemented. bool isFullyImplemented = true; @@ -59,6 +72,18 @@ struct ContractDefinitionAnnotation: TypeDeclarationAnnotation std::set<ContractDefinition const*> contractDependencies; }; +struct FunctionDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation +{ +}; + +struct EventDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation +{ +}; + +struct ModifierDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation +{ +}; + struct VariableDeclarationAnnotation: ASTAnnotation { /// Type of variable (type of identifier referencing this variable). diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 775c7eb6..6b55b408 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -28,6 +28,7 @@ #include <libsolidity/analysis/GlobalContext.h> #include <libsolidity/analysis/NameAndTypeResolver.h> #include <libsolidity/analysis/TypeChecker.h> +#include <libsolidity/analysis/DocStringAnalyser.h> #include <libsolidity/codegen/Compiler.h> #include <libsolidity/interface/CompilerStack.h> #include <libsolidity/interface/InterfaceHandler.h> @@ -114,6 +115,12 @@ bool CompilerStack::parse() resolveImports(); + bool noErrors = true; + DocStringAnalyser docStringAnalyser(m_errors); + for (Source const* source: m_sourceOrder) + if (!docStringAnalyser.analyseDocStrings(*source->ast)) + noErrors = false; + m_globalContext = make_shared<GlobalContext>(); NameAndTypeResolver resolver(m_globalContext->declarations(), m_errors); for (Source const* source: m_sourceOrder) @@ -131,8 +138,6 @@ bool CompilerStack::parse() m_contracts[contract->name()].contract = contract; } - InterfaceHandler interfaceHandler; - bool typesFine = true; for (Source const* source: m_sourceOrder) for (ASTPointer<ASTNode> const& node: source->ast->nodes()) if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) @@ -142,15 +147,15 @@ bool CompilerStack::parse() TypeChecker typeChecker(m_errors); if (typeChecker.checkTypeRequirements(*contract)) { - contract->setDevDocumentation(interfaceHandler.devDocumentation(*contract)); - contract->setUserDocumentation(interfaceHandler.userDocumentation(*contract)); + contract->setDevDocumentation(InterfaceHandler::devDocumentation(*contract)); + contract->setUserDocumentation(InterfaceHandler::userDocumentation(*contract)); } else - typesFine = false; + noErrors = false; m_contracts[contract->name()].contract = contract; } - m_parseSuccessful = typesFine; + m_parseSuccessful = noErrors; return m_parseSuccessful; } @@ -287,7 +292,7 @@ string const& CompilerStack::metadata(string const& _contractName, Documentation // caches the result if (!*doc) - doc->reset(new string(currentContract.interfaceHandler->documentation(*currentContract.contract, _type))); + doc->reset(new string(InterfaceHandler::documentation(*currentContract.contract, _type))); return *(*doc); } @@ -428,8 +433,5 @@ CompilerStack::Source const& CompilerStack::source(string const& _sourceName) co return it->second; } -CompilerStack::Contract::Contract(): interfaceHandler(make_shared<InterfaceHandler>()) {} - - } } diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 1f1b74f5..ac71da2e 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -188,13 +188,10 @@ private: eth::LinkerObject object; eth::LinkerObject runtimeObject; eth::LinkerObject cloneObject; - std::shared_ptr<InterfaceHandler> interfaceHandler; mutable std::unique_ptr<std::string const> interface; mutable std::unique_ptr<std::string const> solidityInterface; mutable std::unique_ptr<std::string const> userDocumentation; mutable std::unique_ptr<std::string const> devDocumentation; - - Contract(); }; void resolveImports(); diff --git a/libsolidity/interface/InterfaceHandler.cpp b/libsolidity/interface/InterfaceHandler.cpp index 136136e6..30cd9724 100644 --- a/libsolidity/interface/InterfaceHandler.cpp +++ b/libsolidity/interface/InterfaceHandler.cpp @@ -3,19 +3,10 @@ #include <boost/range/irange.hpp> #include <libsolidity/ast/AST.h> #include <libsolidity/interface/CompilerStack.h> -using namespace std; - -namespace dev -{ -namespace solidity -{ - -/* -- public -- */ -InterfaceHandler::InterfaceHandler() -{ - m_lastTag = DocTagType::None; -} +using namespace std; +using namespace dev; +using namespace dev::solidity; string InterfaceHandler::documentation( ContractDefinition const& _contractDef, @@ -181,20 +172,18 @@ string InterfaceHandler::userDocumentation(ContractDefinition const& _contractDe Json::Value methods(Json::objectValue); for (auto const& it: _contractDef.interfaceFunctions()) - { - Json::Value user; - auto strPtr = it.second->documentation(); - if (strPtr) - { - resetUser(); - parseDocString(*strPtr, CommentOwner::Function); - if (!m_notice.empty()) - {// since @notice is the only user tag if missing function should not appear - user["notice"] = Json::Value(m_notice); - methods[it.second->externalSignature()] = user; + if (it.second->hasDeclaration()) + if (auto const* f = dynamic_cast<FunctionDefinition const*>(&it.second->declaration())) + { + string value = extractDoc(f->annotation().docTags, "notice"); + if (!value.empty()) + { + Json::Value user; + // since @notice is the only user tag if missing function should not appear + user["notice"] = Json::Value(value); + methods[it.second->externalSignature()] = user; + } } - } - } doc["methods"] = methods; return Json::StyledWriter().write(doc); @@ -202,60 +191,45 @@ string InterfaceHandler::userDocumentation(ContractDefinition const& _contractDe string InterfaceHandler::devDocumentation(ContractDefinition const& _contractDef) { - // LTODO: Somewhere in this function warnings for mismatch of param names - // should be thrown Json::Value doc; Json::Value methods(Json::objectValue); - auto contractDoc = _contractDef.documentation(); - 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; - } + auto author = extractDoc(_contractDef.annotation().docTags, "author"); + if (!author.empty()) + doc["author"] = author; + auto title = extractDoc(_contractDef.annotation().docTags, "title"); + if (!title.empty()) + doc["title"] = title; for (auto const& it: _contractDef.interfaceFunctions()) { + if (!it.second->hasDeclaration()) + continue; Json::Value method; - auto strPtr = it.second->documentation(); - if (strPtr) + if (auto fun = dynamic_cast<FunctionDefinition const*>(&it.second->declaration())) { - resetDev(); - parseDocString(*strPtr, CommentOwner::Function); + auto dev = extractDoc(fun->annotation().docTags, "dev"); + if (!dev.empty()) + method["details"] = Json::Value(dev); - if (!m_dev.empty()) - method["details"] = Json::Value(m_dev); + auto author = extractDoc(fun->annotation().docTags, "author"); + if (!author.empty()) + method["author"] = author; - if (!m_author.empty()) - method["author"] = m_author; + auto ret = extractDoc(fun->annotation().docTags, "return"); + if (!ret.empty()) + method["return"] = ret; Json::Value params(Json::objectValue); - vector<string> paramNames = it.second->parameterNames(); - for (auto const& pair: m_params) - { - if (find(paramNames.begin(), paramNames.end(), pair.first) == paramNames.end()) - // LTODO: mismatching parameter name, throw some form of warning and not just an exception - BOOST_THROW_EXCEPTION( - Error(Error::Type::DocstringParsingError) << - errinfo_comment("documented parameter \"" + pair.first + "\" not found in the parameter list of the function.") - ); - params[pair.first] = pair.second; - } + auto paramRange = fun->annotation().docTags.equal_range("param"); + for (auto i = paramRange.first; i != paramRange.second; ++i) + params[i->second.paramName] = Json::Value(i->second.content); - if (!m_params.empty()) + if (!params.empty()) method["params"] = params; - if (!m_return.empty()) - method["return"] = m_return; - - if (!method.empty()) // add the function, only if we have any documentation to add + if (!method.empty()) + // add the function, only if we have any documentation to add methods[it.second->externalSignature()] = method; } } @@ -264,215 +238,11 @@ string InterfaceHandler::devDocumentation(ContractDefinition const& _contractDef return Json::StyledWriter().write(doc); } -/* -- private -- */ -void InterfaceHandler::resetUser() +string InterfaceHandler::extractDoc(multimap<string, DocTag> const& _tags, string const& _name) { - m_notice.clear(); + string value; + auto range = _tags.equal_range(_name); + for (auto i = range.first; i != range.second; i++) + value += i->second.content; + return value; } - -void InterfaceHandler::resetDev() -{ - m_dev.clear(); - m_author.clear(); - m_return.clear(); - m_params.clear(); -} - -static inline string::const_iterator skipLineOrEOS( - string::const_iterator _nlPos, - string::const_iterator _end -) -{ - return (_nlPos == _end) ? _end : ++_nlPos; -} - -string::const_iterator InterfaceHandler::parseDocTagLine( - string::const_iterator _pos, - string::const_iterator _end, - string& _tagString, - DocTagType _tagType, - bool _appending -) -{ - auto nlPos = find(_pos, _end, '\n'); - if (_appending && _pos < _end && *_pos != ' ') - _tagString += " "; - copy(_pos, nlPos, back_inserter(_tagString)); - m_lastTag = _tagType; - return skipLineOrEOS(nlPos, _end); -} - -string::const_iterator InterfaceHandler::parseDocTagParam( - string::const_iterator _pos, - string::const_iterator _end -) -{ - // find param name - auto currPos = find(_pos, _end, ' '); - if (currPos == _end) - BOOST_THROW_EXCEPTION(Error(Error::Type::DocstringParsingError) << errinfo_comment("End of param name not found" + string(_pos, _end))); - - - auto paramName = string(_pos, currPos); - - currPos += 1; - auto nlPos = find(currPos, _end, '\n'); - auto paramDesc = string(currPos, nlPos); - m_params.push_back(make_pair(paramName, paramDesc)); - - m_lastTag = DocTagType::Param; - return skipLineOrEOS(nlPos, _end); -} - -string::const_iterator InterfaceHandler::appendDocTagParam( - string::const_iterator _pos, - string::const_iterator _end -) -{ - // Should never be called with an empty vector - solAssert(!m_params.empty(), "Internal: Tried to append to empty parameter"); - - auto pair = m_params.back(); - if (_pos < _end && *_pos != ' ') - pair.second += " "; - auto nlPos = find(_pos, _end, '\n'); - copy(_pos, nlPos, back_inserter(pair.second)); - - m_params.at(m_params.size() - 1) = pair; - - return skipLineOrEOS(nlPos, _end); -} - -string::const_iterator InterfaceHandler::parseDocTag( - string::const_iterator _pos, - string::const_iterator _end, - string const& _tag, - CommentOwner _owner -) -{ - // LTODO: need to check for @(start of a tag) between here and the end of line - // 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 (_tag == "dev") - return parseDocTagLine(_pos, _end, m_dev, DocTagType::Dev, false); - else if (_tag == "notice") - return parseDocTagLine(_pos, _end, m_notice, DocTagType::Notice, false); - else if (_tag == "return") - return parseDocTagLine(_pos, _end, m_return, DocTagType::Return, false); - else if (_tag == "author") - { - if (_owner == CommentOwner::Contract) - return parseDocTagLine(_pos, _end, m_contractAuthor, DocTagType::Author, false); - else if (_owner == CommentOwner::Function) - return parseDocTagLine(_pos, _end, m_author, DocTagType::Author, false); - else - // LTODO: for now this else makes no sense but later comments will go to more language constructs - BOOST_THROW_EXCEPTION(Error(Error::Type::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, false); - else - // LTODO: Unknown tag, throw some form of warning and not just an exception - BOOST_THROW_EXCEPTION(Error(Error::Type::DocstringParsingError) << errinfo_comment("@title tag is legal only for contracts")); - } - else if (_tag == "param") - return parseDocTagParam(_pos, _end); - else - // LTODO: Unknown tag, throw some form of warning and not just an exception - BOOST_THROW_EXCEPTION(Error(Error::Type::DocstringParsingError) << errinfo_comment("Unknown tag " + _tag + " encountered")); - } - else - return appendDocTag(_pos, _end, _owner); -} - -string::const_iterator InterfaceHandler::appendDocTag( - string::const_iterator _pos, - string::const_iterator _end, - CommentOwner _owner -) -{ - switch (m_lastTag) - { - case DocTagType::Dev: - return parseDocTagLine(_pos, _end, m_dev, DocTagType::Dev, true); - case DocTagType::Notice: - return parseDocTagLine(_pos, _end, m_notice, DocTagType::Notice, true); - case DocTagType::Return: - return parseDocTagLine(_pos, _end, m_return, DocTagType::Return, true); - case DocTagType::Author: - if (_owner == CommentOwner::Contract) - return parseDocTagLine(_pos, _end, m_contractAuthor, DocTagType::Author, true); - else if (_owner == CommentOwner::Function) - return parseDocTagLine(_pos, _end, m_author, DocTagType::Author, true); - else - // LTODO: Unknown tag, throw some form of warning and not just an exception - BOOST_THROW_EXCEPTION(Error(Error::Type::DocstringParsingError) << errinfo_comment("@author tag in illegal comment")); - case DocTagType::Title: - if (_owner == CommentOwner::Contract) - return parseDocTagLine(_pos, _end, m_title, DocTagType::Title, true); - else - // LTODO: Unknown tag, throw some form of warning and not just an exception - BOOST_THROW_EXCEPTION(Error(Error::Type::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; - } -} - -static inline string::const_iterator firstSpaceOrNl( - string::const_iterator _pos, - string::const_iterator _end -) -{ - auto spacePos = find(_pos, _end, ' '); - auto nlPos = find(_pos, _end, '\n'); - return (spacePos < nlPos) ? spacePos : nlPos; -} - -void InterfaceHandler::parseDocString(string const& _string, CommentOwner _owner) -{ - auto currPos = _string.begin(); - auto end = _string.end(); - - while (currPos != end) - { - auto tagPos = find(currPos, end, '@'); - auto nlPos = find(currPos, end, '\n'); - - if (tagPos != end && tagPos < nlPos) - { - // we found a tag - auto tagNameEndPos = firstSpaceOrNl(tagPos, end); - if (tagNameEndPos == end) - BOOST_THROW_EXCEPTION( - Error(Error::Type::DocstringParsingError) << - errinfo_comment("End of tag " + string(tagPos, tagNameEndPos) + "not found")); - - currPos = parseDocTag(tagNameEndPos + 1, end, string(tagPos + 1, tagNameEndPos), _owner); - } - else if (m_lastTag != DocTagType::None) // continuation of the previous tag - currPos = appendDocTag(currPos, end, _owner); - else if (currPos != end) - { - // if it begins without a tag then consider it as @notice - if (currPos == _string.begin()) - { - currPos = parseDocTag(currPos, end, "notice", CommentOwner::Function); - continue; - } - else if (nlPos == end) //end of text - return; - // else skip the line if a newline was found and we get here - currPos = nlPos + 1; - } - } -} - -} //solidity NS -} // dev NS diff --git a/libsolidity/interface/InterfaceHandler.h b/libsolidity/interface/InterfaceHandler.h index 62164517..30b8f520 100644 --- a/libsolidity/interface/InterfaceHandler.h +++ b/libsolidity/interface/InterfaceHandler.h @@ -37,6 +37,7 @@ namespace solidity // Forward declarations class ContractDefinition; +struct DocTag; enum class DocumentationType: uint8_t; enum class DocTagType: uint8_t @@ -59,73 +60,33 @@ enum class CommentOwner class InterfaceHandler { public: - InterfaceHandler(); - /// Get the given type of documentation /// @param _contractDef The contract definition /// @param _type The type of the documentation. Can be one of the /// types provided by @c DocumentationType /// @return A string with the json representation of provided type - std::string documentation( + static std::string documentation( ContractDefinition const& _contractDef, DocumentationType _type ); /// Get the ABI Interface of the contract /// @param _contractDef The contract definition /// @return A string with the json representation of the contract's ABI Interface - std::string abiInterface(ContractDefinition const& _contractDef); - std::string ABISolidityInterface(ContractDefinition const& _contractDef); + static std::string abiInterface(ContractDefinition const& _contractDef); + static std::string ABISolidityInterface(ContractDefinition const& _contractDef); /// Get the User documentation of the contract /// @param _contractDef The contract definition /// @return A string with the json representation of the contract's user documentation - std::string userDocumentation(ContractDefinition const& _contractDef); + static std::string userDocumentation(ContractDefinition const& _contractDef); /// Genereates the Developer's documentation of the contract /// @param _contractDef The contract definition /// @return A string with the json representation /// of the contract's developer documentation - std::string devDocumentation(ContractDefinition const& _contractDef); + static std::string devDocumentation(ContractDefinition const& _contractDef); private: - void resetUser(); - void resetDev(); - - std::string::const_iterator parseDocTagLine( - std::string::const_iterator _pos, - std::string::const_iterator _end, - std::string& _tagString, - DocTagType _tagType, - bool _appending - ); - std::string::const_iterator parseDocTagParam( - std::string::const_iterator _pos, - std::string::const_iterator _end - ); - std::string::const_iterator appendDocTagParam( - std::string::const_iterator _pos, - std::string::const_iterator _end - ); - void parseDocString(std::string const& _string, CommentOwner _owner); - std::string::const_iterator appendDocTag( - std::string::const_iterator _pos, - std::string::const_iterator _end, - CommentOwner _owner - ); - std::string::const_iterator parseDocTag( - std::string::const_iterator _pos, - std::string::const_iterator _end, - std::string const& _tag, - CommentOwner _owner - ); - - // internal state - DocTagType m_lastTag; - std::string m_notice; - std::string m_dev; - 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; + /// Returns concatenation of all content under the given tag name. + static std::string extractDoc(std::multimap<std::string, DocTag> const& _tags, std::string const& _name); }; } //solidity NS diff --git a/libsolidity/parsing/DocStringParser.cpp b/libsolidity/parsing/DocStringParser.cpp new file mode 100644 index 00000000..bbee35f5 --- /dev/null +++ b/libsolidity/parsing/DocStringParser.cpp @@ -0,0 +1,141 @@ + +#include <libsolidity/parsing/DocStringParser.h> +#include <boost/range/irange.hpp> +#include <libsolidity/interface/Utils.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + + +static inline string::const_iterator skipLineOrEOS( + string::const_iterator _nlPos, + string::const_iterator _end +) +{ + return (_nlPos == _end) ? _end : ++_nlPos; +} + +static inline string::const_iterator firstSpaceOrNl( + string::const_iterator _pos, + string::const_iterator _end +) +{ + auto spacePos = find(_pos, _end, ' '); + auto nlPos = find(_pos, _end, '\n'); + return (spacePos < nlPos) ? spacePos : nlPos; +} + +bool DocStringParser::parse(string const& _docString, ErrorList& _errors) +{ + m_errors = &_errors; + m_errorsOccurred = false; + m_lastTag = nullptr; + + auto currPos = _docString.begin(); + auto end = _docString.end(); + + while (currPos != end) + { + auto tagPos = find(currPos, end, '@'); + auto nlPos = find(currPos, end, '\n'); + + if (tagPos != end && tagPos < nlPos) + { + // we found a tag + auto tagNameEndPos = firstSpaceOrNl(tagPos, end); + if (tagNameEndPos == end) + { + appendError("End of tag " + string(tagPos, tagNameEndPos) + "not found"); + break; + } + + currPos = parseDocTag(tagNameEndPos + 1, end, string(tagPos + 1, tagNameEndPos)); + } + else if (!!m_lastTag) // continuation of the previous tag + currPos = appendDocTag(currPos, end); + else if (currPos != end) + { + // if it begins without a tag then consider it as @notice + if (currPos == _docString.begin()) + { + currPos = parseDocTag(currPos, end, "notice"); + continue; + } + else if (nlPos == end) //end of text + break; + // else skip the line if a newline was found and we get here + currPos = nlPos + 1; + } + } + return !m_errorsOccurred; +} + +DocStringParser::iter DocStringParser::parseDocTagLine(iter _pos, iter _end, bool _appending) +{ + solAssert(!!m_lastTag, ""); + auto nlPos = find(_pos, _end, '\n'); + if (_appending && _pos < _end && *_pos != ' ') + m_lastTag->content += " "; + copy(_pos, nlPos, back_inserter(m_lastTag->content)); + return skipLineOrEOS(nlPos, _end); +} + +DocStringParser::iter DocStringParser::parseDocTagParam(iter _pos, iter _end) +{ + // find param name + auto currPos = find(_pos, _end, ' '); + if (currPos == _end) + { + appendError("End of param name not found" + string(_pos, _end)); + return _end; + } + + auto paramName = string(_pos, currPos); + + currPos += 1; + auto nlPos = find(currPos, _end, '\n'); + auto paramDesc = string(currPos, nlPos); + newTag("param"); + m_lastTag->paramName = paramName; + m_lastTag->content = paramDesc; + + return skipLineOrEOS(nlPos, _end); +} + +DocStringParser::iter DocStringParser::parseDocTag(iter _pos, iter _end, string const& _tag) +{ + // LTODO: need to check for @(start of a tag) between here and the end of line + // for all cases. + if (!m_lastTag || _tag != "") + { + if (_tag == "param") + return parseDocTagParam(_pos, _end); + else + { + newTag(_tag); + return parseDocTagLine(_pos, _end, false); + } + } + else + return appendDocTag(_pos, _end); +} + +DocStringParser::iter DocStringParser::appendDocTag(iter _pos, iter _end) +{ + solAssert(!!m_lastTag, ""); + return parseDocTagLine(_pos, _end, true); +} + +void DocStringParser::newTag(string const& _tagName) +{ + m_lastTag = &m_docTags.insert(make_pair(_tagName, DocTag()))->second; +} + +void DocStringParser::appendError(string const& _description) +{ + auto err = make_shared<Error>(Error::Type::DocstringParsingError); + *err << errinfo_comment(_description); + m_errors->push_back(err); + m_errorsOccurred = true; +} diff --git a/libsolidity/parsing/DocStringParser.h b/libsolidity/parsing/DocStringParser.h new file mode 100644 index 00000000..f67b8bbd --- /dev/null +++ b/libsolidity/parsing/DocStringParser.h @@ -0,0 +1,70 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @author Lefteris <lefteris@ethdev.com> + * @date 2014, 2015 + * Parses a given docstring into pieces introduced by tags. + */ + +#pragma once + +#include <string> +#include <libsolidity/interface/Exceptions.h> +#include <libsolidity/ast/ASTAnnotations.h> + +namespace dev +{ +namespace solidity +{ + +class DocStringParser +{ +public: + /// Parse the given @a _docString and stores the parsed components internally. + /// @returns false on error and appends the error to @a _errors. + bool parse(std::string const& _docString, ErrorList& _errors); + + std::multimap<std::string, DocTag> const& tags() const { return m_docTags; } + +private: + using iter = std::string::const_iterator; + void resetUser(); + void resetDev(); + + iter parseDocTagLine(iter _pos, iter _end, bool _appending); + iter parseDocTagParam(iter _pos, iter _end); + iter appendDocTagParam(iter _pos, iter _end); + void parseDocString(std::string const& _string); + iter appendDocTag(iter _pos, iter _end); + /// Parses the doc tag named @a _tag, adds it to m_docTags and returns the position + /// after the tag. + iter parseDocTag(iter _pos, iter _end, std::string const& _tag); + + /// Creates and inserts a new tag and adjusts m_lastTag. + void newTag(std::string const& _tagName); + + void appendError(std::string const& _description); + + /// Mapping tag name -> content. + std::multimap<std::string, DocTag> m_docTags; + DocTag* m_lastTag = nullptr; + ErrorList* m_errors = nullptr; + bool m_errorsOccurred = false; +}; + +} //solidity NS +} // dev NS |