From ef6a76ce672378a95803409c84c69e121393052e Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Fri, 1 Feb 2019 15:39:18 +0100 Subject: [PATCH 1/2] libdevcore: Introduces a generalized AnsiColorized, an improved FormattedScope a future commit/PR could replace existing code to use AnsiColorized and remove the old implementation of FormattedScope. --- libdevcore/AnsiColorized.h | 91 ++++++++++++++++++++++++++++++++++++++ libdevcore/CMakeLists.txt | 1 + 2 files changed, 92 insertions(+) create mode 100644 libdevcore/AnsiColorized.h diff --git a/libdevcore/AnsiColorized.h b/libdevcore/AnsiColorized.h new file mode 100644 index 000000000..0ee54ab0a --- /dev/null +++ b/libdevcore/AnsiColorized.h @@ -0,0 +1,91 @@ +/* + 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 . +*/ + +#pragma once + +#include +#include + +namespace dev +{ + +namespace formatting +{ + +// control codes +static constexpr char const* RESET = "\033[0m"; +static constexpr char const* INVERSE = "\033[7m"; +static constexpr char const* BOLD = "\033[1m"; +static constexpr char const* BRIGHT = BOLD; + +// standard foreground colors +static constexpr char const* BLACK = "\033[30m"; +static constexpr char const* RED = "\033[31m"; +static constexpr char const* GREEN = "\033[32m"; +static constexpr char const* YELLOW = "\033[33m"; +static constexpr char const* BLUE = "\033[34m"; +static constexpr char const* MAGENTA = "\033[35m"; +static constexpr char const* CYAN = "\033[36m"; +static constexpr char const* WHITE = "\033[37m"; + +// standard background colors +static constexpr char const* BLACK_BACKGROUND = "\033[40m"; +static constexpr char const* RED_BACKGROUND = "\033[41m"; +static constexpr char const* GREEN_BACKGROUND = "\033[42m"; +static constexpr char const* YELLOW_BACKGROUND = "\033[43m"; +static constexpr char const* BLUE_BACKGROUND = "\033[44m"; +static constexpr char const* MAGENTA_BACKGROUND = "\033[45m"; +static constexpr char const* CYAN_BACKGROUND = "\033[46m"; +static constexpr char const* WHITE_BACKGROUND = "\033[47m"; + +// 256-bit-colors (incomplete set) +static constexpr char const* RED_BACKGROUND_256 = "\033[48;5;160m"; +static constexpr char const* ORANGE_BACKGROUND_256 = "\033[48;5;166m"; + +} + +/// AnsiColorized provides a convenience helper to colorize ostream with formatting-reset assured. +class AnsiColorized +{ +public: + AnsiColorized(std::ostream& _os, bool const _enabled, std::vector&& _formatting): + m_stream{_os}, m_enabled{_enabled}, m_codes{std::move(_formatting)} + { + if (m_enabled) + for (auto const& code: m_codes) + m_stream << code; + } + + ~AnsiColorized() + { + if (m_enabled) + m_stream << formatting::RESET; + } + + template + std::ostream& operator<<(T&& _t) + { + return m_stream << std::forward(_t); + } + +private: + std::ostream& m_stream; + bool m_enabled; + std::vector m_codes; +}; + +} diff --git a/libdevcore/CMakeLists.txt b/libdevcore/CMakeLists.txt index 193fa41d0..b92cb5db5 100644 --- a/libdevcore/CMakeLists.txt +++ b/libdevcore/CMakeLists.txt @@ -1,5 +1,6 @@ set(sources Algorithms.h + AnsiColorized.h Assertions.h Common.h CommonData.cpp From 3d4b0f45daa27aa89dd2a3763119f717e42dc598 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Tue, 27 Nov 2018 00:21:53 +0100 Subject: [PATCH 2/2] liblangutil: refactors SourceReferenceFormatter error formatting for pretty and colored output. * Refactors output format in a way it is (or should at least be) more readable. (NB.: As source of inspiration, I chose the rustc compiler output.) * Adds color support to the stream output. * Also improves multiline source formatting (i.e. truncating too long lines, like done with single lines already) * solc: adds flags --color (force terminal colors) and --no-color (disable autodetection) * solc: adds --new-reporter to give output in *new* formatting (colored or not) * Changelog adapted accordingly. --- Changelog.md | 3 +- liblangutil/CMakeLists.txt | 2 + liblangutil/SourceReferenceFormatter.h | 13 +- liblangutil/SourceReferenceFormatterHuman.cpp | 136 ++++++++++++++++++ liblangutil/SourceReferenceFormatterHuman.h | 80 +++++++++++ solc/CommandLineInterface.cpp | 40 +++++- solc/CommandLineInterface.h | 2 + 7 files changed, 264 insertions(+), 12 deletions(-) create mode 100644 liblangutil/SourceReferenceFormatterHuman.cpp create mode 100644 liblangutil/SourceReferenceFormatterHuman.h diff --git a/Changelog.md b/Changelog.md index 1ec6faf1e..89143ee59 100644 --- a/Changelog.md +++ b/Changelog.md @@ -13,7 +13,8 @@ Language Features: Compiler Features: * C API (``libsolc`` / raw ``soljson.js``): Introduce ``solidity_free`` method which releases all internal buffers to save memory. - + * Commandline interface: Adds new option ``--new-reporter`` for improved diagnostics formatting + along with ``--color`` and ``--no-color`` for colorized output to be forced (or explicitly disabled). Bugfixes: diff --git a/liblangutil/CMakeLists.txt b/liblangutil/CMakeLists.txt index b172108b3..4539376f0 100644 --- a/liblangutil/CMakeLists.txt +++ b/liblangutil/CMakeLists.txt @@ -16,6 +16,8 @@ set(sources SourceReferenceExtractor.h SourceReferenceFormatter.cpp SourceReferenceFormatter.h + SourceReferenceFormatterHuman.cpp + SourceReferenceFormatterHuman.h Token.cpp Token.h UndefMacros.h diff --git a/liblangutil/SourceReferenceFormatter.h b/liblangutil/SourceReferenceFormatter.h index 9f05f4303..c01b360eb 100644 --- a/liblangutil/SourceReferenceFormatter.h +++ b/liblangutil/SourceReferenceFormatter.h @@ -44,11 +44,14 @@ public: m_stream(_stream) {} + virtual ~SourceReferenceFormatter() = default; + /// Prints source location if it is given. - void printSourceLocation(SourceLocation const* _location); - void printSourceLocation(SourceReference const& _ref); - void printExceptionInformation(dev::Exception const& _error, std::string const& _category); - void printExceptionInformation(SourceReferenceExtractor::Message const& _msg); + virtual void printSourceLocation(SourceReference const& _ref); + virtual void printExceptionInformation(SourceReferenceExtractor::Message const& _msg); + + virtual void printSourceLocation(SourceLocation const* _location); + virtual void printExceptionInformation(dev::Exception const& _error, std::string const& _category); static std::string formatExceptionInformation( dev::Exception const& _exception, @@ -62,7 +65,7 @@ public: return errorOutput.str(); } -private: +protected: /// Prints source name if location is given. void printSourceName(SourceReference const& _ref); diff --git a/liblangutil/SourceReferenceFormatterHuman.cpp b/liblangutil/SourceReferenceFormatterHuman.cpp new file mode 100644 index 000000000..f246c2641 --- /dev/null +++ b/liblangutil/SourceReferenceFormatterHuman.cpp @@ -0,0 +1,136 @@ +/* + 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 . +*/ +/** + * Formatting functions for errors referencing positions and locations in the source. + */ + +#include +#include +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::formatting; +using namespace langutil; + +AnsiColorized SourceReferenceFormatterHuman::normalColored() const +{ + return AnsiColorized(m_stream, m_colored, {WHITE}); +} + +AnsiColorized SourceReferenceFormatterHuman::frameColored() const +{ + return AnsiColorized(m_stream, m_colored, {BOLD, BLUE}); +} + +AnsiColorized SourceReferenceFormatterHuman::errorColored() const +{ + return AnsiColorized(m_stream, m_colored, {BOLD, RED}); +} + +AnsiColorized SourceReferenceFormatterHuman::messageColored() const +{ + return AnsiColorized(m_stream, m_colored, {BOLD, WHITE}); +} + +AnsiColorized SourceReferenceFormatterHuman::secondaryColored() const +{ + return AnsiColorized(m_stream, m_colored, {BOLD, CYAN}); +} + +AnsiColorized SourceReferenceFormatterHuman::highlightColored() const +{ + return AnsiColorized(m_stream, m_colored, {YELLOW}); +} + +AnsiColorized SourceReferenceFormatterHuman::diagColored() const +{ + return AnsiColorized(m_stream, m_colored, {BOLD, YELLOW}); +} + +void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _ref) +{ + if (_ref.position.line < 0) + return; // Nothing we can print here + + int const leftpad = static_cast(log10(max(_ref.position.line, 1))) + 1; + + // line 0: source name + frameColored() << string(leftpad, ' ') << "--> "; + m_stream << _ref.sourceName << ":" << (_ref.position.line + 1) << ":" << (_ref.position.column + 1) << ": " << '\n'; + + if (!_ref.multiline) + { + int const locationLength = _ref.endColumn - _ref.startColumn; + + // line 1: + m_stream << string(leftpad, ' '); + frameColored() << " |" << '\n'; + + // line 2: + frameColored() << (_ref.position.line + 1) << " | "; + m_stream << _ref.text.substr(0, _ref.startColumn); + highlightColored() << _ref.text.substr(_ref.startColumn, locationLength); + m_stream << _ref.text.substr(_ref.endColumn) << '\n'; + + // line 3: + m_stream << string(leftpad, ' '); + frameColored() << " | "; + for_each( + _ref.text.cbegin(), + _ref.text.cbegin() + _ref.startColumn, + [this](char ch) { m_stream << (ch == '\t' ? '\t' : ' '); } + ); + diagColored() << string(locationLength, '^') << '\n'; + } + else + { + // line 1: + m_stream << string(leftpad, ' '); + frameColored() << " |" << '\n'; + + // line 2: + frameColored() << (_ref.position.line + 1) << " | "; + m_stream << _ref.text.substr(0, _ref.startColumn); + highlightColored() << _ref.text.substr(_ref.startColumn) << '\n'; + + // line 3: + frameColored() << string(leftpad, ' ') << " | "; + m_stream << string(_ref.startColumn, ' '); + diagColored() << "^ (Relevant source part starts here and spans across multiple lines).\n"; + } +} + +void SourceReferenceFormatterHuman::printExceptionInformation(SourceReferenceExtractor::Message const& _msg) +{ + // exception header line + errorColored() << _msg.category; + messageColored() << ": " << _msg.primary.message << '\n'; + + printSourceLocation(_msg.primary); + + for (auto const& secondary: _msg.secondary) + { + secondaryColored() << "Note"; + messageColored() << ": " << secondary.message << '\n'; + printSourceLocation(secondary); + } + + m_stream << '\n'; +} diff --git a/liblangutil/SourceReferenceFormatterHuman.h b/liblangutil/SourceReferenceFormatterHuman.h new file mode 100644 index 000000000..a5e803894 --- /dev/null +++ b/liblangutil/SourceReferenceFormatterHuman.h @@ -0,0 +1,80 @@ +/* + 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 . +*/ +/** + * Formatting functions for errors referencing positions and locations in the source. + */ + +#pragma once + +#include +#include // SourceReferenceFormatterBase + +#include + +#include +#include +#include + +namespace dev +{ +struct Exception; // forward +} + +namespace langutil +{ + +struct SourceLocation; +struct SourceReference; + +class SourceReferenceFormatterHuman: public SourceReferenceFormatter +{ +public: + SourceReferenceFormatterHuman(std::ostream& _stream, bool colored): + SourceReferenceFormatter{_stream}, m_colored{colored} + {} + + void printSourceLocation(SourceReference const& _ref) override; + void printExceptionInformation(SourceReferenceExtractor::Message const& _msg) override; + using SourceReferenceFormatter::printExceptionInformation; + + static std::string formatExceptionInformation( + dev::Exception const& _exception, + std::string const& _name, + bool colored = false + ) + { + std::ostringstream errorOutput; + + SourceReferenceFormatterHuman formatter(errorOutput, colored); + formatter.printExceptionInformation(_exception, _name); + return errorOutput.str(); + } + +private: + dev::AnsiColorized normalColored() const; + dev::AnsiColorized frameColored() const; + dev::AnsiColorized errorColored() const; + dev::AnsiColorized messageColored() const; + dev::AnsiColorized secondaryColored() const; + dev::AnsiColorized highlightColored() const; + dev::AnsiColorized diagColored() const; + +private: + bool m_colored; +}; + +} diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 4bf24901b..8fb3abb47 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -46,6 +47,8 @@ #include #include +#include + #include #include #include @@ -134,6 +137,9 @@ static string const g_strStrictAssembly = "strict-assembly"; static string const g_strPrettyJson = "pretty-json"; static string const g_strVersion = "version"; static string const g_strIgnoreMissingFiles = "ignore-missing"; +static string const g_strColor = "color"; +static string const g_strNoColor = "no-color"; +static string const g_strNewReporter = "new-reporter"; static string const g_argAbi = g_strAbi; static string const g_argPrettyJson = g_strPrettyJson; @@ -169,6 +175,9 @@ static string const g_argStrictAssembly = g_strStrictAssembly; static string const g_argVersion = g_strVersion; static string const g_stdinFileName = g_stdinFileNameStr; static string const g_argIgnoreMissingFiles = g_strIgnoreMissingFiles; +static string const g_argColor = g_strColor; +static string const g_argNoColor = g_strNoColor; +static string const g_argNewReporter = g_strNewReporter; /// Possible arguments to for --combined-json static set const g_combinedJsonArgs @@ -652,6 +661,9 @@ Allowed options)", po::value()->value_name("path(s)"), "Allow a given path for imports. A list of paths can be supplied by separating them with a comma." ) + (g_argColor.c_str(), "Force colored output.") + (g_argNoColor.c_str(), "Explicitly disable colored output, disabling terminal auto-detection.") + (g_argNewReporter.c_str(), "Enables new diagnostics reporter.") (g_argIgnoreMissingFiles.c_str(), "Ignore missing files."); po::options_description outputComponents("Output Components"); outputComponents.add_options() @@ -691,6 +703,14 @@ Allowed options)", return false; } + if (m_args.count(g_argColor) && m_args.count(g_argNoColor)) + { + serr() << "Option " << g_argColor << " and " << g_argNoColor << " are mutualy exclusive." << endl; + return false; + } + + m_coloredOutput = !m_args.count(g_argNoColor) && (isatty(STDERR_FILENO) || m_args.count(g_argColor)); + if (m_args.count(g_argHelp) || (isatty(fileno(stdin)) && _argc == 1)) { sout() << desc; @@ -858,7 +878,11 @@ bool CommandLineInterface::processInput() m_compiler.reset(new CompilerStack(fileReader)); - SourceReferenceFormatter formatter(serr(false)); + unique_ptr formatter; + if (m_args.count(g_argNewReporter)) + formatter = make_unique(serr(false), m_coloredOutput); + else + formatter = make_unique(serr(false)); try { @@ -881,7 +905,7 @@ bool CommandLineInterface::processInput() for (auto const& error: m_compiler->errors()) { g_hasOutput = true; - formatter.printExceptionInformation( + formatter->printExceptionInformation( *error, (error->type() == Error::Type::Warning) ? "Warning" : "Error" ); @@ -893,7 +917,7 @@ bool CommandLineInterface::processInput() catch (CompilerError const& _exception) { g_hasOutput = true; - formatter.printExceptionInformation(_exception, "Compiler error"); + formatter->printExceptionInformation(_exception, "Compiler error"); return false; } catch (InternalCompilerError const& _exception) @@ -915,7 +939,7 @@ bool CommandLineInterface::processInput() else { g_hasOutput = true; - formatter.printExceptionInformation(_error, _error.typeName()); + formatter->printExceptionInformation(_error, _error.typeName()); } return false; @@ -1221,12 +1245,16 @@ bool CommandLineInterface::assemble( for (auto const& sourceAndStack: assemblyStacks) { auto const& stack = sourceAndStack.second; - SourceReferenceFormatter formatter(serr(false)); + unique_ptr formatter; + if (m_args.count(g_argNewReporter)) + formatter = make_unique(serr(false), m_coloredOutput); + else + formatter = make_unique(serr(false)); for (auto const& error: stack.errors()) { g_hasOutput = true; - formatter.printExceptionInformation( + formatter->printExceptionInformation( *error, (error->type() == Error::Type::Warning) ? "Warning" : "Error" ); diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h index ff294adc8..2b74f3e5e 100644 --- a/solc/CommandLineInterface.h +++ b/solc/CommandLineInterface.h @@ -109,6 +109,8 @@ private: std::unique_ptr m_compiler; /// EVM version to use EVMVersion m_evmVersion; + /// Whether or not to colorize diagnostics output. + bool m_coloredOutput = true; }; }