From 953e8a178b5150a93c825f47d8fb25e7af883231 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Mon, 3 Jun 2019 13:25:51 +0200 Subject: [PATCH] WIP --- libsolidity/codegen/ir/IRGenerator.cpp | 7 +- libyul/AsmAnalysis.h | 2 +- libyul/AsmPrinter.cpp | 40 +++++++- libyul/AsmPrinter.h | 1 + libyul/AssemblyStack.cpp | 15 ++- libyul/AssemblyStack.h | 5 + libyul/optimiser/SyntacticalEquality.cpp | 5 - libyul/optimiser/SyntacticalEquality.h | 3 +- tools/yul-format/main.cpp | 122 +++++++++++++---------- 9 files changed, 135 insertions(+), 65 deletions(-) diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index acf74805a..686aa0204 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -45,7 +45,6 @@ using namespace dev::solidity; pair IRGenerator::run(ContractDefinition const& _contract) { - // TODO Would be nice to pretty-print this while retaining comments. string ir = generate(_contract); yul::AssemblyStack asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings); @@ -56,6 +55,10 @@ pair IRGenerator::run(ContractDefinition const& _contract) errorMessage += langutil::SourceReferenceFormatter::formatErrorInformation(*error); solAssert(false, "Invalid IR generated:\n" + errorMessage + "\n" + ir); } + + // NB: pretty-print is retaining documentation comments (those with tripple-slash). + string const irPrettyPrinted = asmStack.parseTree().toString(true); + asmStack.optimize(); string warning = @@ -66,7 +69,7 @@ pair IRGenerator::run(ContractDefinition const& _contract) " * !USE AT YOUR OWN RISK! *\n" " *******************************************************/\n\n"; - return {warning + ir, warning + asmStack.print()}; + return {warning + irPrettyPrinted, warning + asmStack.print()}; } string IRGenerator::generate(ContractDefinition const& _contract) diff --git a/libyul/AsmAnalysis.h b/libyul/AsmAnalysis.h index f74d2b81b..8e5fd4e75 100644 --- a/libyul/AsmAnalysis.h +++ b/libyul/AsmAnalysis.h @@ -94,7 +94,7 @@ public: bool operator()(Break const&); bool operator()(Continue const&); bool operator()(Block const& _block); - bool operator()(Comment const& _comment) { return true; } + bool operator()(Comment const&) { return true; } private: /// Visits the statement and expects it to deposit one item onto the stack. diff --git a/libyul/AsmPrinter.cpp b/libyul/AsmPrinter.cpp index 1dc5ba449..4903fd859 100644 --- a/libyul/AsmPrinter.cpp +++ b/libyul/AsmPrinter.cpp @@ -243,13 +243,23 @@ string AsmPrinter::operator()(Continue const&) const string AsmPrinter::operator()(Block const& _block) const { + size_t const outerBlockStatementIndex = exchange(m_blockStatementIndex, 0); + ScopeGuard const _restoreBlockStatementIndex{[=]() { m_blockStatementIndex = outerBlockStatementIndex; }}; + auto const incrementBlockStatementIndex = [&](string _s) -> string + { + ++m_blockStatementIndex; + return _s; + }; + if (_block.statements.empty()) return "{ }"; string body = boost::algorithm::join( - _block.statements | boost::adaptors::transformed(boost::apply_visitor(*this)), + _block.statements | + boost::adaptors::transformed(boost::apply_visitor(*this)) | + boost::adaptors::transformed(incrementBlockStatementIndex), "\n" ); - if (body.size() < 30 && body.find('\n') == string::npos) + if (body.size() < 30 && body.find('\n') == string::npos && body.find("//") == string::npos) return "{ " + body + " }"; else { @@ -258,10 +268,30 @@ string AsmPrinter::operator()(Block const& _block) const } } -std::string AsmPrinter::operator()(Comment const& _comment) const +string AsmPrinter::operator()(Comment const& _comment) const { - // Leading newline for better readability of comments. - return "\n// " + _comment.text; + auto const static trimWhitespace = [](string _s) -> string + { + auto const static isnospace = [](int _ch) { return !std::isspace(_ch); }; + _s.erase(_s.begin(), std::find_if(_s.begin(), _s.end(), isnospace)); + _s.erase(find_if(_s.rbegin(), _s.rend(), isnospace).base(), _s.end()); + return _s; + }; + auto const trimmedText = trimWhitespace(_comment.text); + + vector lines; + boost::split(lines, trimmedText, [](auto _ch) -> bool { return _ch == '\n'; }); + + stringstream out; + + if (m_blockStatementIndex > 0) + out << '\n'; // One leading newline for better readability of comments. + + for (size_t i = 0; i < lines.size() - 1; ++i) + out << "// " << trimWhitespace(lines[i]) + "\n"; + + out << "// " << trimWhitespace(lines.back()); + return out.str(); } string AsmPrinter::formatTypedName(TypedName _variable) const diff --git a/libyul/AsmPrinter.h b/libyul/AsmPrinter.h index 93ac285ac..a5bb4a18c 100644 --- a/libyul/AsmPrinter.h +++ b/libyul/AsmPrinter.h @@ -60,6 +60,7 @@ private: std::string appendTypeName(YulString _type) const; bool m_yul = false; + mutable size_t m_blockStatementIndex; }; } diff --git a/libyul/AssemblyStack.cpp b/libyul/AssemblyStack.cpp index 549a599fe..665a7cb05 100644 --- a/libyul/AssemblyStack.cpp +++ b/libyul/AssemblyStack.cpp @@ -75,7 +75,7 @@ Scanner const& AssemblyStack::scanner() const return *m_scanner; } -bool AssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string const& _source) +bool AssemblyStack::parse(std::string const& _sourceName, std::string const& _source) { m_errors.clear(); m_analysisSuccessful = false; @@ -85,6 +85,19 @@ bool AssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string return false; solAssert(m_parserResult, ""); solAssert(m_parserResult->code, ""); + return true; +} + +yul::Object const& AssemblyStack::parseTree() const noexcept +{ + yulAssert(m_parserResult, "Parser result must be available."); + return *m_parserResult; +} + +bool AssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string const& _source) +{ + if (!parse(_sourceName, _source)) + return false; return analyzeParsed(); } diff --git a/libyul/AssemblyStack.h b/libyul/AssemblyStack.h index 8367ce786..6500b317a 100644 --- a/libyul/AssemblyStack.h +++ b/libyul/AssemblyStack.h @@ -73,6 +73,8 @@ public: /// @returns the scanner used during parsing langutil::Scanner const& scanner() const; + bool parse(std::string const& _sourceName, std::string const& _source); + /// Runs parsing and analysis steps, returns false if input cannot be assembled. /// Multiple calls overwrite the previous state. bool parseAndAnalyze(std::string const& _sourceName, std::string const& _source); @@ -84,6 +86,9 @@ public: /// Run the assembly step (should only be called after parseAndAnalyze). MachineAssemblyObject assemble(Machine _machine) const; + /// @returns parsed source code as AST. + yul::Object const& parseTree() const noexcept; + /// @returns the errors generated during parsing, analysis (and potentially assembly). langutil::ErrorList const& errors() const { return m_errors; } diff --git a/libyul/optimiser/SyntacticalEquality.cpp b/libyul/optimiser/SyntacticalEquality.cpp index 3673328bb..9e425b17e 100644 --- a/libyul/optimiser/SyntacticalEquality.cpp +++ b/libyul/optimiser/SyntacticalEquality.cpp @@ -181,11 +181,6 @@ bool SyntacticallyEqual::statementEqual(Block const& _lhs, Block const& _rhs) }); } -bool SyntacticallyEqual::statementEqual(Comment const& _lhs, Comment const& _rhs) -{ - return _lhs.text == _rhs.text; -} - bool SyntacticallyEqual::visitDeclaration(TypedName const& _lhs, TypedName const& _rhs) { if (_lhs.type != _rhs.type) diff --git a/libyul/optimiser/SyntacticalEquality.h b/libyul/optimiser/SyntacticalEquality.h index 3a4391ef8..1d237014f 100644 --- a/libyul/optimiser/SyntacticalEquality.h +++ b/libyul/optimiser/SyntacticalEquality.h @@ -58,7 +58,8 @@ public: bool statementEqual(Break const&, Break const&) { return true; } bool statementEqual(Continue const&, Continue const&) { return true; } bool statementEqual(Block const& _lhs, Block const& _rhs); - bool statementEqual(Comment const& _lhs, Comment const& _rhs); + bool statementEqual(Comment const&, Comment const&) { return true; } + private: bool statementEqual(Instruction const& _lhs, Instruction const& _rhs); bool statementEqual(Label const& _lhs, Label const& _rhs); diff --git a/tools/yul-format/main.cpp b/tools/yul-format/main.cpp index c92e0cdce..0516a41e3 100644 --- a/tools/yul-format/main.cpp +++ b/tools/yul-format/main.cpp @@ -1,3 +1,20 @@ +/* + This file is part of solidity. + + solidity 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. + + solidity 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 solidity. If not, see . +*/ + #include #include #include @@ -18,34 +35,31 @@ using namespace std; namespace po = boost::program_options; -boost::optional prettyPrint( - std::string const& _sourceCode, - std::string const& _sourceName, - yul::EVMDialect const& _evmDialect, - langutil::ErrorReporter& _errorReporter -) -{ - langutil::EVMVersion const evmVersion = *langutil::EVMVersion::fromString("petersburg"); - yul::EVMDialect const& evmDialect = yul::EVMDialect::strictAssemblyForEVM(evmVersion); - yul::Parser parser{_errorReporter, evmDialect}; - langutil::CharStream source{_sourceCode, _sourceName}; - auto scanner = make_shared(source); - shared_ptr ast = parser.parse(scanner, true); - - if (_errorReporter.hasErrors()) - return boost::none; - else - return {yul::AsmPrinter{true}(*ast)}; -} - struct Flags { langutil::EVMVersion evmVersion; + yul::AssemblyStack::Language language; std::string sourceName; std::string sourceCode; }; -boost::optional parseArgs(int argc, const char* argv[]) +static boost::optional parseAssemblyLanguageId(string const& name) +{ + using Language = yul::AssemblyStack::Language; + + if (name == "yul") + return {Language::Yul}; + else if (name == "assembly") + return {Language::Assembly}; + else if (name == "strict-assembly") + return {Language::StrictAssembly}; + else if (name == "ewasm") + return {Language::EWasm}; + + return boost::none; +} + +boost::optional parseArgs(int _argc, char const* _argv[]) { po::options_description options( R"(yul-format, the Yul source code pretty printer. @@ -57,12 +71,13 @@ Allowed options)", po::options_description::m_default_line_length - 23); options.add_options() - ("help", "Show this help screen.") + ("help,h", "Show this help screen.") ( "evm-version", po::value()->value_name("version"), "Select desired EVM version. Either homestead, tangerineWhistle, spuriousDragon, byzantium, constantinople or petersburg (default)." ) + ("lang", po::value(), "Language to format. One of yul, assembly, strict-assembly, ewasm.") ("input-file", po::value(), "Input file to format."); po::positional_options_description filesPositions; @@ -71,7 +86,7 @@ Allowed options)", po::variables_map arguments; try { - po::command_line_parser cmdLineParser(argc, argv); + po::command_line_parser cmdLineParser(_argc, _argv); cmdLineParser.options(options).positional(filesPositions); po::store(cmdLineParser.run(), arguments); } @@ -87,7 +102,7 @@ Allowed options)", return boost::none; } - langutil::EVMVersion evmVersion = *langutil::EVMVersion::fromString("petersburg"); + langutil::EVMVersion evmVersion = langutil::EVMVersion::petersburg(); if (arguments.count("evm-version")) { string versionOptionStr = arguments["evm-version"].as(); @@ -99,40 +114,47 @@ Allowed options)", } evmVersion = *value; } - if (arguments.count("input-file")) - return Flags{ - evmVersion, - arguments["input-file"].as(), - dev::readFileAsString(arguments["input-file"].as()) - }; - else - return Flags{ - evmVersion, - "stdin", - dev::readStandardInput() - }; + yul::AssemblyStack::Language lang = yul::AssemblyStack::Language::Yul; + if (arguments.count("lang")) + { + auto parsedLangId = parseAssemblyLanguageId(arguments["lang"].as()); + if (!parsedLangId) + { + cerr << "Invalid language ID. Try --help.\n"; + return boost::none; + } + else + lang = *parsedLangId; + } + tuple const input = [&]() -> tuple + { + if (arguments.count("input-file")) + return make_tuple( + arguments["input-file"].as(), + dev::readFileAsString(arguments["input-file"].as()) + ); + else + return make_tuple(string{"stdin"}, dev::readStandardInput()); + }(); + return Flags{ + evmVersion, + lang, + get<0>(input), + get<1>(input) + }; } -int main(int argc, const char* argv[]) +int main(int argc, char const* argv[]) { boost::optional flags = parseArgs(argc, argv); if (!flags) return EXIT_FAILURE; - langutil::ErrorList errors; - langutil::ErrorReporter errorReporter{errors}; - - optional const pretty = prettyPrint( - flags->sourceCode, - flags->sourceName, - yul::EVMDialect::strictAssemblyForEVM(flags->evmVersion), - errorReporter - ); - - if (!errorReporter.hasErrors()) - cout << *pretty; + yul::AssemblyStack stack{flags->evmVersion, flags->language, {}}; + if (stack.parse(flags->sourceName, flags->sourceCode)) + cout << yul::AsmPrinter{true}(*stack.parseTree().code); else - for (shared_ptr const& error : errors) + for (shared_ptr const& error : stack.errors()) langutil::SourceReferenceFormatterHuman{cerr, true}.printErrorInformation(*error); return EXIT_SUCCESS;