diff options
-rw-r--r-- | AST.h | 2 | ||||
-rw-r--r-- | CMakeLists.txt | 4 | ||||
-rw-r--r-- | CompilerStack.cpp | 66 | ||||
-rw-r--r-- | CompilerStack.h | 26 | ||||
-rw-r--r-- | Exceptions.h | 1 | ||||
-rw-r--r-- | InterfaceHandler.cpp | 278 | ||||
-rw-r--r-- | InterfaceHandler.h | 110 |
7 files changed, 445 insertions, 42 deletions
@@ -238,7 +238,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 ea2ef4b7..b5147ced 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,10 @@ endif() include_directories(..) 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 198ded09..62172384 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; @@ -127,45 +128,34 @@ void CompilerStack::streamAssembly(ostream& _outStream, string const& _contractN string const& CompilerStack::getInterface(std::string const& _contractName) { + return getJsonDocumentation(_contractName, ABI_INTERFACE); +} + +std::string const& CompilerStack::getJsonDocumentation(std::string const& _contractName, enum DocumentationType _type) +{ + if (!m_parseSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); + Contract& contract = getContract(_contractName); - if (contract.interface.empty()) + + std::unique_ptr<string>* doc; + switch (_type) { - stringstream interface; - interface << '['; - vector<FunctionDefinition const*> exportedFunctions = contract.contract->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 << ']'; - contract.interface = interface.str(); + case NATSPEC_USER: + doc = &contract.userDocumentation; + break; + case NATSPEC_DEV: + doc = &contract.devDocumentation; + break; + case ABI_INTERFACE: + doc = &contract.interface; + break; + default: + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Illegal documentation type.")); } - return contract.interface; + if (!*doc) + *doc = contract.interfaceHandler->getDocumentation(*contract.contract, _type); + return *(*doc); } Scanner const& CompilerStack::getScanner(string const& _sourceName) @@ -193,7 +183,6 @@ void CompilerStack::reset(bool _keepSources) else m_sources.clear(); m_globalContext.reset(); - m_compiler.reset(); m_sourceOrder.clear(); m_contracts.clear(); } @@ -247,5 +236,8 @@ CompilerStack::Source& CompilerStack::getSource(string const& _sourceName) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Given source file not found.")); return it->second; } + +CompilerStack::Contract::Contract(): interfaceHandler(make_shared<InterfaceHandler>()) {} + } } diff --git a/CompilerStack.h b/CompilerStack.h index a5b8ec41..34178841 100644 --- a/CompilerStack.h +++ b/CompilerStack.h @@ -33,10 +33,18 @@ namespace solidity { // forward declarations class Scanner; +class ContractDefinition; class SourceUnit; class Compiler; class GlobalContext; -class ContractDefinition; +class InterfaceHandler; + +enum DocumentationType: unsigned short +{ + NATSPEC_USER = 1, + NATSPEC_DEV, + ABI_INTERFACE +}; /** * Easy to use and self-contained Solidity compiler with as few header dependencies as possible. @@ -47,6 +55,7 @@ class CompilerStack: boost::noncopyable { public: CompilerStack(): m_parseSuccessful(false) {} + /// Adds a source object (e.g. file) to the parser. After this, parse has to be called again. void addSource(std::string const& _name, std::string const& _content); void setSource(std::string const& _sourceCode); @@ -71,6 +80,11 @@ public: /// Returns a string representing the contract interface in JSON. /// Prerequisite: Successful call to parse or compile. std::string const& getInterface(std::string const& _contractName = ""); + /// 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 DocumentationType + std::string const& getJsonDocumentation(std::string const& _contractName, enum DocumentationType _type); /// Returns the previously used scanner, useful for counting lines during error reporting. Scanner const& getScanner(std::string const& _sourceName = ""); @@ -94,10 +108,15 @@ private: struct Contract { - ContractDefinition const* contract; - std::string interface; + ContractDefinition* contract; std::shared_ptr<Compiler> compiler; bytes bytecode; + std::shared_ptr<InterfaceHandler> interfaceHandler; + std::unique_ptr<std::string> interface; + std::unique_ptr<std::string> userDocumentation; + std::unique_ptr<std::string> devDocumentation; + + Contract(); }; void reset(bool _keepSources = false); @@ -109,7 +128,6 @@ private: bool m_parseSuccessful; std::map<std::string, Source> m_sources; std::shared_ptr<GlobalContext> m_globalContext; - std::shared_ptr<Compiler> m_compiler; std::vector<Source const*> m_sourceOrder; std::map<std::string, Contract> m_contracts; }; diff --git a/Exceptions.h b/Exceptions.h index ffd8a72d..14f91977 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_sourceLocation, Location> errinfo_sourceLocation; diff --git a/InterfaceHandler.cpp b/InterfaceHandler.cpp new file mode 100644 index 00000000..95e8c58a --- /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 = DOCTAG_NONE; +} + +std::unique_ptr<std::string> InterfaceHandler::getDocumentation(ContractDefinition& _contractDef, + enum DocumentationType _type) +{ + switch(_type) + { + case NATSPEC_USER: + return getUserDocumentation(_contractDef); + case NATSPEC_DEV: + return getDevDocumentation(_contractDef); + case ABI_INTERFACE: + return getABIInterface(_contractDef); + } + + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown documentation type")); + return nullptr; +} + +std::unique_ptr<std::string> InterfaceHandler::getABIInterface(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(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(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, + enum 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 = DOCTAG_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 == DOCTAG_NONE || _tag != "") + { + if (_tag == "dev") + return parseDocTagLine(_pos, _end, m_dev, DOCTAG_DEV); + else if (_tag == "notice") + return parseDocTagLine(_pos, _end, m_notice, DOCTAG_NOTICE); + else if (_tag == "return") + return parseDocTagLine(_pos, _end, m_return, DOCTAG_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 DOCTAG_DEV: + m_dev += " "; + return parseDocTagLine(_pos, _end, m_dev, DOCTAG_DEV); + case DOCTAG_NOTICE: + m_notice += " "; + return parseDocTagLine(_pos, _end, m_notice, DOCTAG_NOTICE); + case DOCTAG_RETURN: + m_return += " "; + return parseDocTagLine(_pos, _end, m_return, DOCTAG_RETURN); + case DOCTAG_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 != DOCTAG_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..0eae3aaf --- /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 DocumentationType: unsigned short; + +enum DocTagType +{ + DOCTAG_NONE = 0, + DOCTAG_DEV, + DOCTAG_NOTICE, + DOCTAG_PARAM, + DOCTAG_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(ContractDefinition& _contractDef, + enum 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(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(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(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, + enum 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 + enum 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 |