diff options
-rw-r--r-- | AST.h | 2 | ||||
-rw-r--r-- | CMakeLists.txt | 4 | ||||
-rw-r--r-- | CompilerStack.cpp | 60 | ||||
-rw-r--r-- | CompilerStack.h | 20 | ||||
-rw-r--r-- | Exceptions.h | 1 | ||||
-rw-r--r-- | InterfaceHandler.cpp | 278 | ||||
-rw-r--r-- | InterfaceHandler.h | 110 |
7 files changed, 435 insertions, 40 deletions
@@ -204,7 +204,7 @@ public: Block& getBody() { return *m_body; } /// @return A shared pointer of an ASTString. /// Can contain a nullptr in which case indicates absence of documentation - ASTPointer<ASTString> const& getDocumentation() { return m_documentation; } + ASTPointer<ASTString> const& getDocumentation() const { return m_documentation; } void addLocalVariable(VariableDeclaration const& _localVariable) { m_localVariables.push_back(&_localVariable); } std::vector<VariableDeclaration const*> const& getLocalVariables() const { return m_localVariables; } diff --git a/CMakeLists.txt b/CMakeLists.txt index 8522130e..0f2dbbcd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,10 @@ else() endif() target_link_libraries(${EXECUTABLE} evmcore devcore) +# TODO: Temporary until PR 532 https://github.com/ethereum/cpp-ethereum/pull/532 +# gets accepted. Then we can simply add jsoncpp as a dependency and not the +# whole of JSONRPC as we are doing right here +target_link_libraries(${EXECUTABLE} ${JSONRPC_LS}) install( TARGETS ${EXECUTABLE} ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) diff --git a/CompilerStack.cpp b/CompilerStack.cpp index 6535e00d..01bf99d9 100644 --- a/CompilerStack.cpp +++ b/CompilerStack.cpp @@ -27,6 +27,7 @@ #include <libsolidity/NameAndTypeResolver.h> #include <libsolidity/Compiler.h> #include <libsolidity/CompilerStack.h> +#include <libsolidity/InterfaceHandler.h> using namespace std; @@ -35,6 +36,8 @@ namespace dev namespace solidity { +CompilerStack::CompilerStack(): m_interfaceHandler(make_shared<InterfaceHandler>()) {} + void CompilerStack::setSource(string const& _sourceCode) { reset(); @@ -81,48 +84,31 @@ void CompilerStack::streamAssembly(ostream& _outStream) m_compiler->streamAssembly(_outStream); } -string const& CompilerStack::getInterface() +std::string const& CompilerStack::getJsonDocumentation(DocumentationType _type) { if (!m_parseSuccessful) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); - if (m_interface.empty()) + + auto createDocIfNotThere = [this, _type](std::unique_ptr<string>& _doc) { - stringstream interface; - interface << '['; - vector<FunctionDefinition const*> exportedFunctions = m_contractASTNode->getInterfaceFunctions(); - unsigned functionsCount = exportedFunctions.size(); - for (FunctionDefinition const* f: exportedFunctions) - { - auto streamVariables = [&](vector<ASTPointer<VariableDeclaration>> const& _vars) - { - unsigned varCount = _vars.size(); - for (ASTPointer<VariableDeclaration> const& var: _vars) - { - interface << "{" - << "\"name\":" << escaped(var->getName(), false) << "," - << "\"type\":" << escaped(var->getType()->toString(), false) - << "}"; - if (--varCount > 0) - interface << ","; - } - }; - - interface << '{' - << "\"name\":" << escaped(f->getName(), false) << "," - << "\"inputs\":["; - streamVariables(f->getParameters()); - interface << "]," - << "\"outputs\":["; - streamVariables(f->getReturnParameters()); - interface << "]" - << "}"; - if (--functionsCount > 0) - interface << ","; - } - interface << ']'; - m_interface = interface.str(); + if (!_doc) + _doc = m_interfaceHandler->getDocumentation(m_contractASTNode, _type); + }; + + switch (_type) + { + case DocumentationType::NATSPEC_USER: + createDocIfNotThere(m_userDocumentation); + return *m_userDocumentation; + case DocumentationType::NATSPEC_DEV: + createDocIfNotThere(m_devDocumentation); + return *m_devDocumentation; + case DocumentationType::ABI_INTERFACE: + createDocIfNotThere(m_interface); + return *m_interface; } - return m_interface; + + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Illegal documentation type.")); } bytes CompilerStack::staticCompile(std::string const& _sourceCode, bool _optimize) diff --git a/CompilerStack.h b/CompilerStack.h index 6cae8660..928815cc 100644 --- a/CompilerStack.h +++ b/CompilerStack.h @@ -35,6 +35,14 @@ class Scanner; class ContractDefinition; class Compiler; class GlobalContext; +class InterfaceHandler; + +enum class DocumentationType: uint8_t +{ + NATSPEC_USER = 1, + NATSPEC_DEV, + ABI_INTERFACE +}; /** * Easy to use and self-contained Solidity compiler with as few header dependencies as possible. @@ -44,7 +52,7 @@ class GlobalContext; class CompilerStack { public: - CompilerStack() {} + CompilerStack(); void reset() { *this = CompilerStack(); } void setSource(std::string const& _sourceCode); void parse(); @@ -62,6 +70,11 @@ public: /// Returns a string representing the contract interface in JSON. /// Prerequisite: Successful call to parse or compile. std::string const& getInterface(); + /// Returns a string representing the contract's documentation in JSON. + /// Prerequisite: Successful call to parse or compile. + /// @param type The type of the documentation to get. + /// Can be one of 3 types defined at @c documentation_type + std::string const& getJsonDocumentation(DocumentationType type); /// Returns the previously used scanner, useful for counting lines during error reporting. Scanner const& getScanner() const { return *m_scanner; } @@ -76,8 +89,11 @@ private: std::shared_ptr<GlobalContext> m_globalContext; std::shared_ptr<ContractDefinition> m_contractASTNode; bool m_parseSuccessful; - std::string m_interface; + std::unique_ptr<std::string> m_interface; + std::unique_ptr<std::string> m_userDocumentation; + std::unique_ptr<std::string> m_devDocumentation; std::shared_ptr<Compiler> m_compiler; + std::shared_ptr<InterfaceHandler> m_interfaceHandler; bytes m_bytecode; }; diff --git a/Exceptions.h b/Exceptions.h index 1903c1dc..8298c981 100644 --- a/Exceptions.h +++ b/Exceptions.h @@ -36,6 +36,7 @@ struct TypeError: virtual Exception {}; struct DeclarationError: virtual Exception {}; struct CompilerError: virtual Exception {}; struct InternalCompilerError: virtual Exception {}; +struct DocstringParsingError: virtual Exception {}; typedef boost::error_info<struct tag_sourcePosition, int> errinfo_sourcePosition; typedef boost::error_info<struct tag_sourceLocation, Location> errinfo_sourceLocation; diff --git a/InterfaceHandler.cpp b/InterfaceHandler.cpp new file mode 100644 index 00000000..18c053cb --- /dev/null +++ b/InterfaceHandler.cpp @@ -0,0 +1,278 @@ + +#include <libsolidity/InterfaceHandler.h> +#include <libsolidity/AST.h> +#include <libsolidity/CompilerStack.h> + +namespace dev +{ +namespace solidity +{ + +/* -- public -- */ + +InterfaceHandler::InterfaceHandler() +{ + m_lastTag = DocTagType::NONE; +} + +std::unique_ptr<std::string> InterfaceHandler::getDocumentation(std::shared_ptr<ContractDefinition> _contractDef, + DocumentationType _type) +{ + switch(_type) + { + case DocumentationType::NATSPEC_USER: + return getUserDocumentation(_contractDef); + case DocumentationType::NATSPEC_DEV: + return getDevDocumentation(_contractDef); + case DocumentationType::ABI_INTERFACE: + return getABIInterface(_contractDef); + } + + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown documentation type")); + return nullptr; +} + +std::unique_ptr<std::string> InterfaceHandler::getABIInterface(std::shared_ptr<ContractDefinition> _contractDef) +{ + Json::Value methods(Json::arrayValue); + + for (FunctionDefinition const* f: _contractDef->getInterfaceFunctions()) + { + Json::Value method; + Json::Value inputs(Json::arrayValue); + Json::Value outputs(Json::arrayValue); + + auto populateParameters = [](std::vector<ASTPointer<VariableDeclaration>> const& _vars) + { + Json::Value params(Json::arrayValue); + for (ASTPointer<VariableDeclaration> const& var: _vars) + { + Json::Value input; + input["name"] = var->getName(); + input["type"] = var->getType()->toString(); + params.append(input); + } + return params; + }; + + method["name"] = f->getName(); + method["inputs"] = populateParameters(f->getParameters()); + method["outputs"] = populateParameters(f->getReturnParameters()); + methods.append(method); + } + return std::unique_ptr<std::string>(new std::string(m_writer.write(methods))); +} + +std::unique_ptr<std::string> InterfaceHandler::getUserDocumentation(std::shared_ptr<ContractDefinition> _contractDef) +{ + Json::Value doc; + Json::Value methods(Json::objectValue); + + for (FunctionDefinition const* f: _contractDef->getInterfaceFunctions()) + { + Json::Value user; + auto strPtr = f->getDocumentation(); + if (strPtr) + { + resetUser(); + parseDocString(*strPtr); + if (!m_notice.empty()) + {// since @notice is the only user tag if missing function should not appear + user["notice"] = Json::Value(m_notice); + methods[f->getName()] = user; + } + } + } + doc["methods"] = methods; + + return std::unique_ptr<std::string>(new std::string(m_writer.write(doc))); +} + +std::unique_ptr<std::string> InterfaceHandler::getDevDocumentation(std::shared_ptr<ContractDefinition> _contractDef) +{ + // LTODO: Somewhere in this function warnings for mismatch of param names + // should be thrown + Json::Value doc; + Json::Value methods(Json::objectValue); + + for (FunctionDefinition const* f: _contractDef->getInterfaceFunctions()) + { + Json::Value method; + auto strPtr = f->getDocumentation(); + if (strPtr) + { + resetDev(); + parseDocString(*strPtr); + + if (!m_dev.empty()) + method["details"] = Json::Value(m_dev); + Json::Value params(Json::objectValue); + for (auto const& pair: m_params) + params[pair.first] = pair.second; + + if (!m_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 + methods[f->getName()] = method; + } + } + doc["methods"] = methods; + + return std::unique_ptr<std::string>(new std::string(m_writer.write(doc))); +} + +/* -- private -- */ +void InterfaceHandler::resetUser() +{ + m_notice.clear(); +} + +void InterfaceHandler::resetDev() +{ + m_dev.clear(); + m_return.clear(); + m_params.clear(); +} + +static inline std::string::const_iterator skipLineOrEOS(std::string::const_iterator _nlPos, + std::string::const_iterator _end) +{ + return (_nlPos == _end) ? _end : ++_nlPos; +} + +std::string::const_iterator InterfaceHandler::parseDocTagLine(std::string::const_iterator _pos, + std::string::const_iterator _end, + std::string& _tagString, + DocTagType _tagType) +{ + auto nlPos = std::find(_pos, _end, '\n'); + std::copy(_pos, nlPos, back_inserter(_tagString)); + m_lastTag = _tagType; + return skipLineOrEOS(nlPos, _end); +} + +std::string::const_iterator InterfaceHandler::parseDocTagParam(std::string::const_iterator _pos, + std::string::const_iterator _end) +{ + // find param name + auto currPos = std::find(_pos, _end, ' '); + if (currPos == _end) + BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("End of param name not found" + std::string(_pos, _end))); + + + auto paramName = std::string(_pos, currPos); + + currPos += 1; + auto nlPos = std::find(currPos, _end, '\n'); + auto paramDesc = std::string(currPos, nlPos); + m_params.push_back(std::make_pair(paramName, paramDesc)); + + m_lastTag = DocTagType::PARAM; + return skipLineOrEOS(nlPos, _end); +} + +std::string::const_iterator InterfaceHandler::appendDocTagParam(std::string::const_iterator _pos, + std::string::const_iterator _end) +{ + // Should never be called with an empty vector + if (asserts(!m_params.empty())) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Internal: Tried to append to empty parameter")); + + auto pair = m_params.back(); + pair.second += " "; + auto nlPos = std::find(_pos, _end, '\n'); + std::copy(_pos, nlPos, back_inserter(pair.second)); + + m_params.at(m_params.size() - 1) = pair; + + return skipLineOrEOS(nlPos, _end); +} + +std::string::const_iterator InterfaceHandler::parseDocTag(std::string::const_iterator _pos, + std::string::const_iterator _end, + std::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 == DocTagType::NONE || _tag != "") + { + if (_tag == "dev") + return parseDocTagLine(_pos, _end, m_dev, DocTagType::DEV); + else if (_tag == "notice") + return parseDocTagLine(_pos, _end, m_notice, DocTagType::NOTICE); + else if (_tag == "return") + return parseDocTagLine(_pos, _end, m_return, DocTagType::RETURN); + 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(DocstringParsingError() << errinfo_comment("Unknown tag " + _tag + " encountered")); + } + } + else + return appendDocTag(_pos, _end); +} + +std::string::const_iterator InterfaceHandler::appendDocTag(std::string::const_iterator _pos, + std::string::const_iterator _end) +{ + switch (m_lastTag) + { + case DocTagType::DEV: + m_dev += " "; + return parseDocTagLine(_pos, _end, m_dev, DocTagType::DEV); + case DocTagType::NOTICE: + m_notice += " "; + return parseDocTagLine(_pos, _end, m_notice, DocTagType::NOTICE); + case DocTagType::RETURN: + m_return += " "; + return parseDocTagLine(_pos, _end, m_return, DocTagType::RETURN); + case DocTagType::PARAM: + return appendDocTagParam(_pos, _end); + default: + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Internal: Illegal documentation tag type")); + break; + } +} + +static inline std::string::const_iterator getFirstSpaceOrNl(std::string::const_iterator _pos, + std::string::const_iterator _end) +{ + auto spacePos = std::find(_pos, _end, ' '); + auto nlPos = std::find(_pos, _end, '\n'); + return (spacePos < nlPos) ? spacePos : nlPos; +} + +void InterfaceHandler::parseDocString(std::string const& _string) +{ + auto currPos = _string.begin(); + auto end = _string.end(); + + while (currPos != end) + { + auto tagPos = std::find(currPos, end, '@'); + auto nlPos = std::find(currPos, end, '\n'); + + if (tagPos != end && tagPos < nlPos) + { + // we found a tag + auto tagNameEndPos = getFirstSpaceOrNl(tagPos, end); + if (tagNameEndPos == end) + BOOST_THROW_EXCEPTION(DocstringParsingError() << + errinfo_comment("End of tag " + std::string(tagPos, tagNameEndPos) + "not found")); + + currPos = parseDocTag(tagNameEndPos + 1, end, std::string(tagPos + 1, tagNameEndPos)); + } + else if (m_lastTag != DocTagType::NONE) // continuation of the previous tag + currPos = appendDocTag(currPos + 1, end); + else if (currPos != end) // skip the line if a newline was found + currPos = nlPos + 1; + } +} + +} //solidity NS +} // dev NS diff --git a/InterfaceHandler.h b/InterfaceHandler.h new file mode 100644 index 00000000..524e2903 --- /dev/null +++ b/InterfaceHandler.h @@ -0,0 +1,110 @@ +/* + 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 + * Takes the parsed AST and produces the Natspec + * documentation and the ABI interface + * https://github.com/ethereum/wiki/wiki/Ethereum-Natural-Specification-Format + * + * Can generally deal with JSON files + */ + +#pragma once + +#include <string> +#include <memory> +#include <jsonrpc/json/json.h> + +namespace dev +{ +namespace solidity +{ + +// Forward declarations +class ContractDefinition; +enum class DocumentationType: uint8_t; + +enum class DocTagType: uint8_t +{ + NONE = 0, + DEV, + NOTICE, + PARAM, + RETURN +}; + +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 unique pointer contained string with the json + /// representation of provided type + std::unique_ptr<std::string> getDocumentation(std::shared_ptr<ContractDefinition> _contractDef, + DocumentationType _type); + /// Get the ABI Interface of the contract + /// @param _contractDef The contract definition + /// @return A unique pointer contained string with the json + /// representation of the contract's ABI Interface + std::unique_ptr<std::string> getABIInterface(std::shared_ptr<ContractDefinition> _contractDef); + /// Get the User documentation of the contract + /// @param _contractDef The contract definition + /// @return A unique pointer contained string with the json + /// representation of the contract's user documentation + std::unique_ptr<std::string> getUserDocumentation(std::shared_ptr<ContractDefinition> _contractDef); + /// Get the Developer's documentation of the contract + /// @param _contractDef The contract definition + /// @return A unique pointer contained string with the json + /// representation of the contract's developer documentation + std::unique_ptr<std::string> getDevDocumentation(std::shared_ptr<ContractDefinition> _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); + 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); + std::string::const_iterator appendDocTag(std::string::const_iterator _pos, + std::string::const_iterator _end); + std::string::const_iterator parseDocTag(std::string::const_iterator _pos, + std::string::const_iterator _end, + std::string const& _tag); + + Json::StyledWriter m_writer; + + // internal state + DocTagType m_lastTag; + std::string m_notice; + std::string m_dev; + std::string m_return; + std::vector<std::pair<std::string, std::string>> m_params; +}; + +} //solidity NS +} // dev NS |