Implement diagnostic color printing

For linux and windows consoles.

Still needs hooking up to `samples/main.cc`

Bug: tint:282
Change-Id: If8430572708ea7d8788ef05d5379886be89fcb17
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/31564
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
This commit is contained in:
Ben Clayton 2020-11-02 19:26:28 +00:00 committed by Commit Bot service account
parent 2d89d98fe1
commit 2e6cc992c8
10 changed files with 588 additions and 29 deletions

View File

@ -381,6 +381,8 @@ source_set("libtint_core_src") {
"src/diagnostic/diagnostic.h", "src/diagnostic/diagnostic.h",
"src/diagnostic/formatter.cc", "src/diagnostic/formatter.cc",
"src/diagnostic/formatter.h", "src/diagnostic/formatter.h",
"src/diagnostic/printer.cc",
"src/diagnostic/printer.h",
"src/inspector/entry_point.cc", "src/inspector/entry_point.cc",
"src/inspector/entry_point.h", "src/inspector/entry_point.h",
"src/inspector/inspector.cc", "src/inspector/inspector.cc",
@ -418,6 +420,14 @@ source_set("libtint_core_src") {
"src/writer/writer.h", "src/writer/writer.h",
] ]
if (is_linux) {
sources += [ "src/diagnostic/printer_linux.cc" ]
} else if (is_win) {
sources += [ "src/diagnostic/printer_windows.cc" ]
} else {
sources += [ "src/diagnostic/printer_other.cc" ]
}
public_deps = [ public_deps = [
":tint_core_enums_unified1", ":tint_core_enums_unified1",
":tint_core_tables_unified1", ":tint_core_tables_unified1",
@ -772,6 +782,7 @@ source_set("tint_unittests_core_src") {
"src/ast/variable_test.cc", "src/ast/variable_test.cc",
"src/ast/workgroup_decoration_test.cc", "src/ast/workgroup_decoration_test.cc",
"src/diagnostic/formatter_test.cc", "src/diagnostic/formatter_test.cc",
"src/diagnostic/printer_test.cc",
"src/inspector/inspector_test.cc", "src/inspector/inspector_test.cc",
"src/scope_stack_test.cc", "src/scope_stack_test.cc",
"src/transform/bound_array_accessors_transform_test.cc", "src/transform/bound_array_accessors_transform_test.cc",

View File

@ -202,6 +202,8 @@ set(TINT_LIB_SRCS
diagnostic/diagnostic.h diagnostic/diagnostic.h
diagnostic/formatter.cc diagnostic/formatter.cc
diagnostic/formatter.h diagnostic/formatter.h
diagnostic/printer.cc
diagnostic/printer.h
inspector/entry_point.cc inspector/entry_point.cc
inspector/entry_point.h inspector/entry_point.h
inspector/inspector.cc inspector/inspector.cc
@ -239,6 +241,14 @@ set(TINT_LIB_SRCS
writer/writer.h writer/writer.h
) )
if(UNIX)
list(APPEND TINT_LIB_SRCS diagnostic/printer_linux.cc)
elseif(WIN32)
list(APPEND TINT_LIB_SRCS diagnostic/printer_windows.cc)
else()
list(APPEND TINT_LIB_SRCS diagnostic/printer_other.cc)
endif()
if(${TINT_BUILD_SPV_READER}) if(${TINT_BUILD_SPV_READER})
list(APPEND TINT_LIB_SRCS list(APPEND TINT_LIB_SRCS
reader/spirv/construct.h reader/spirv/construct.h
@ -382,6 +392,7 @@ set(TINT_TEST_SRCS
ast/variable_test.cc ast/variable_test.cc
ast/workgroup_decoration_test.cc ast/workgroup_decoration_test.cc
diagnostic/formatter_test.cc diagnostic/formatter_test.cc
diagnostic/printer_test.cc
inspector/inspector_test.cc inspector/inspector_test.cc
scope_stack_test.cc scope_stack_test.cc
transform/bound_array_accessors_transform_test.cc transform/bound_array_accessors_transform_test.cc

View File

@ -59,76 +59,137 @@ std::basic_ostream<CharT, Traits>& operator<<(
class BasicFormatter : public Formatter { class BasicFormatter : public Formatter {
public: public:
struct State {
explicit State(Printer* p) : printer(p) {}
~State() { flush(); }
// set_style() sets the current style to new_style, flushing any pending
// messages to the printer if the style changed.
void set_style(const Style& new_style) {
if (style.color != new_style.color || style.bold != new_style.bold) {
flush();
style = new_style;
}
}
// flush() writes any pending messages to the printer, clearing the buffer.
void flush() {
auto str = stream.str();
if (str.length() > 0) {
printer->write(str, style);
std::stringstream reset;
stream.swap(reset);
}
}
// operator<<() queues msg to be written to the printer.
template <typename T>
State& operator<<(const T& msg) {
stream << msg;
return *this;
}
// newline() queues a newline to be written to the printer.
void newline() { stream << std::endl; }
// repeat() queues the character c to be writen to the printer n times.
void repeat(char c, size_t n) {
while (n-- > 0) {
stream << c;
}
}
private:
Printer* printer;
Style style;
std::stringstream stream;
};
BasicFormatter(bool print_file, bool print_severity, bool print_line) BasicFormatter(bool print_file, bool print_severity, bool print_line)
: print_file_(print_file), : print_file_(print_file),
print_severity_(print_severity), print_severity_(print_severity),
print_line_(print_line) {} print_line_(print_line) {}
std::string format(const List& list) const override { void format(const List& list, Printer* printer) const override {
State state{printer};
bool first = true; bool first = true;
std::stringstream ss;
for (auto diag : list) { for (auto diag : list) {
state.set_style({});
if (!first) { if (!first) {
ss << std::endl; state.newline();
} }
format(diag, ss); format(diag, &state);
first = false; first = false;
} }
return ss.str();
} }
private: private:
void format(const Diagnostic& diag, std::stringstream& ss) const { void format(const Diagnostic& diag, State* state) const {
auto const& src = diag.source; auto const& src = diag.source;
auto const& rng = src.range; auto const& rng = src.range;
state->set_style({Color::kDefault, true});
if (print_file_ && src.file != nullptr && !src.file->path.empty()) { if (print_file_ && src.file != nullptr && !src.file->path.empty()) {
ss << src.file->path; (*state) << src.file->path;
if (rng.begin.line > 0) { if (rng.begin.line > 0) {
ss << ":" << rng.begin; (*state) << ":" << rng.begin;
} }
} else { } else {
ss << rng.begin; (*state) << rng.begin;
} }
if (print_severity_) { if (print_severity_) {
ss << " " << diag.severity; switch (diag.severity) {
case Severity::Warning:
state->set_style({Color::kYellow, true});
break;
case Severity::Error:
case Severity::Fatal:
state->set_style({Color::kRed, true});
break;
default:
break;
} }
ss << ": " << diag.message; (*state) << " " << diag.severity;
}
state->set_style({Color::kDefault, true});
(*state) << ": " << diag.message;
if (print_line_ && src.file != nullptr && rng.begin.line > 0) { if (print_line_ && src.file != nullptr && rng.begin.line > 0) {
ss << std::endl; state->newline();
state->set_style({Color::kDefault, false});
for (size_t line = rng.begin.line; line <= rng.end.line; line++) { for (size_t line = rng.begin.line; line <= rng.end.line; line++) {
if (line < src.file->lines.size() + 1) { if (line < src.file->lines.size() + 1) {
auto len = src.file->lines[line - 1].size(); auto len = src.file->lines[line - 1].size();
ss << src.file->lines[line - 1]; (*state) << src.file->lines[line - 1];
ss << std::endl; state->newline();
state->set_style({Color::kCyan, false});
if (line == rng.begin.line && line == rng.end.line) { if (line == rng.begin.line && line == rng.end.line) {
// Single line // Single line
repeat(' ', rng.begin.column - 1, ss); state->repeat(' ', rng.begin.column - 1);
repeat('^', std::max<size_t>(rng.end.column - rng.begin.column, 1), state->repeat(
ss); '^', std::max<size_t>(rng.end.column - rng.begin.column, 1));
} else if (line == rng.begin.line) { } else if (line == rng.begin.line) {
// Start of multi-line // Start of multi-line
repeat(' ', rng.begin.column - 1, ss); state->repeat(' ', rng.begin.column - 1);
repeat('^', len - (rng.begin.column - 1), ss); state->repeat('^', len - (rng.begin.column - 1));
} else if (line == rng.end.line) { } else if (line == rng.end.line) {
// End of multi-line // End of multi-line
repeat('^', rng.end.column - 1, ss); state->repeat('^', rng.end.column - 1);
} else { } else {
// Middle of multi-line // Middle of multi-line
repeat('^', len, ss); state->repeat('^', len);
}
ss << std::endl;
}
} }
state->newline();
} }
} }
void repeat(char c, size_t n, std::stringstream& ss) const { state->set_style({});
while (n-- > 0) {
ss << c;
} }
} }

View File

@ -18,6 +18,8 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include "src/diagnostic/printer.h"
namespace tint { namespace tint {
namespace diag { namespace diag {
@ -37,8 +39,18 @@ class Formatter {
virtual ~Formatter(); virtual ~Formatter();
/// @return the human readable list of diagnostics formatted to a string. /// format prints the formatted diagnostic list |list| to |printer|.
virtual std::string format(const List&) const = 0; /// @param list the list of diagnostic messages to format
/// @param printer the printer used to display the formatted diagnostics
virtual void format(const List& list, Printer* printer) const = 0;
/// @return the list of diagnostics |list| formatted to a string.
/// @param list the list of diagnostic messages to format
inline std::string format(const List& list) const {
StringPrinter printer;
format(list, &printer);
return printer.str();
}
}; };
} // namespace diag } // namespace diag

36
src/diagnostic/printer.cc Normal file
View File

@ -0,0 +1,36 @@
// Copyright 2020 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/diagnostic/printer.h"
#include <cstring>
namespace tint {
namespace diag {
Printer::~Printer() = default;
StringPrinter::StringPrinter() = default;
StringPrinter::~StringPrinter() = default;
std::string StringPrinter::str() const {
return stream.str();
}
void StringPrinter::write(const std::string& str, const Style&) {
stream << str;
}
} // namespace diag
} // namespace tint

83
src/diagnostic/printer.h Normal file
View File

@ -0,0 +1,83 @@
// Copyright 2020 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SRC_DIAGNOSTIC_PRINTER_H_
#define SRC_DIAGNOSTIC_PRINTER_H_
#include <memory>
#include <sstream>
#include <string>
namespace tint {
namespace diag {
class List;
/// Color is an enumerator of colors used by Style.
enum class Color {
kDefault,
kBlack,
kRed,
kGreen,
kYellow,
kBlue,
kMagenta,
kCyan,
kWhite,
};
/// Style describes how a diagnostic message should be printed.
struct Style {
/// The foreground text color
Color color = Color::kDefault;
/// If true the text will be displayed with a strong weight
bool bold = false;
};
/// Printers are used to print formatted diagnostic messages to a terminal.
class Printer {
public:
/// @returns a diagnostic Printer
/// @param out the file to print to.
/// @param use_colors if true, the printer will use colors if |out| is a
/// terminal and supports them.
static std::unique_ptr<Printer> create(FILE* out, bool use_colors);
virtual ~Printer();
/// writes the string str to the printer with the given style.
/// @param str the string to write to the printer
/// @param style the style used to print |str|
virtual void write(const std::string& str, const Style& style) = 0;
};
/// StringPrinter is an implementation of Printer that writes to a std::string.
class StringPrinter : public Printer {
public:
StringPrinter();
~StringPrinter() override;
/// @returns the printed string.
std::string str() const;
void write(const std::string& str, const Style&) override;
private:
std::stringstream stream;
};
} // namespace diag
} // namespace tint
#endif // SRC_DIAGNOSTIC_PRINTER_H_

View File

@ -0,0 +1,100 @@
// Copyright 2020 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/diagnostic/printer.h"
#include <unistd.h>
#include <cstring>
namespace tint {
namespace diag {
namespace {
bool supports_colors(FILE* f) {
if (!isatty(fileno(f))) {
return false;
}
const char* cterm = getenv("TERM");
if (cterm == nullptr) {
return false;
}
std::string term = getenv("TERM");
if (term != "cygwin" && term != "linux" && term != "rxvt-unicode-256color" &&
term != "rxvt-unicode" && term != "screen-256color" && term != "screen" &&
term != "tmux-256color" && term != "tmux" && term != "xterm-256color" &&
term != "xterm-color" && term != "xterm") {
return false;
}
return true;
}
class PrinterLinux : public Printer {
public:
PrinterLinux(FILE* f, bool colors)
: file(f), use_colors(colors && supports_colors(f)) {}
void write(const std::string& str, const Style& style) override {
write_color(style.color, style.bold);
fwrite(str.data(), 1, str.size(), file);
write_color(Color::kDefault, false);
}
private:
constexpr const char* color_code(Color color, bool bold) {
switch (color) {
case Color::kDefault:
return bold ? "\u001b[1m" : "\u001b[0m";
case Color::kBlack:
return bold ? "\u001b[30;1m" : "\u001b[30m";
case Color::kRed:
return bold ? "\u001b[31;1m" : "\u001b[31m";
case Color::kGreen:
return bold ? "\u001b[32;1m" : "\u001b[32m";
case Color::kYellow:
return bold ? "\u001b[33;1m" : "\u001b[33m";
case Color::kBlue:
return bold ? "\u001b[34;1m" : "\u001b[34m";
case Color::kMagenta:
return bold ? "\u001b[35;1m" : "\u001b[35m";
case Color::kCyan:
return bold ? "\u001b[36;1m" : "\u001b[36m";
case Color::kWhite:
return bold ? "\u001b[37;1m" : "\u001b[37m";
}
return ""; // unreachable
}
void write_color(Color color, bool bold) {
if (use_colors) {
auto* code = color_code(color, bold);
fwrite(code, 1, strlen(code), file);
}
}
FILE* const file;
bool const use_colors;
};
} // namespace
std::unique_ptr<Printer> Printer::create(FILE* out, bool use_colors) {
return std::make_unique<PrinterLinux>(out, use_colors);
}
} // namespace diag
} // namespace tint

View File

@ -0,0 +1,42 @@
// Copyright 2020 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/diagnostic/printer.h"
#include <cstring>
namespace tint {
namespace diag {
namespace {
class PrinterOther : public Printer {
public:
explicit PrinterOther(FILE* f) : file(f) {}
void write(const std::string& str, const Style&) override {
fwrite(str.data(), 1, str.size(), file);
}
private:
FILE* file;
};
} // namespace
std::unique_ptr<Printer> Printer::create(FILE* out, bool) {
return std::make_unique<PrinterOther>(out);
}
} // namespace diag
} // namespace tint

View File

@ -0,0 +1,90 @@
// Copyright 2020 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/diagnostic/printer.h"
#include "gtest/gtest.h"
namespace tint {
namespace diag {
namespace {
using PrinterTest = testing::Test;
// Actually verifying that the expected colors are printed is exceptionally
// difficult as:
// a) The color emission varies by OS.
// b) The logic checks to see if the printer is writing to a terminal, making
// mocking hard.
// c) Actually probing what gets written to a FILE* is notoriously tricky.
//
// The least we can do is to exersice the code - which is what we do here.
// The test will print each of the colors, and can be examined with human
// eyeballs.
TEST_F(PrinterTest, WithColors) {
auto printer = Printer::create(stdout, true);
printer->write("Default", Style{Color::kDefault, false});
printer->write("Black", Style{Color::kBlack, false});
printer->write("Red", Style{Color::kRed, false});
printer->write("Green", Style{Color::kGreen, false});
printer->write("Yellow", Style{Color::kYellow, false});
printer->write("Blue", Style{Color::kBlue, false});
printer->write("Magenta", Style{Color::kMagenta, false});
printer->write("Cyan", Style{Color::kCyan, false});
printer->write("White", Style{Color::kWhite, false});
}
TEST_F(PrinterTest, BoldWithColors) {
auto printer = Printer::create(stdout, true);
printer->write("Default", Style{Color::kDefault, true});
printer->write("Black", Style{Color::kBlack, true});
printer->write("Red", Style{Color::kRed, true});
printer->write("Green", Style{Color::kGreen, true});
printer->write("Yellow", Style{Color::kYellow, true});
printer->write("Blue", Style{Color::kBlue, true});
printer->write("Magenta", Style{Color::kMagenta, true});
printer->write("Cyan", Style{Color::kCyan, true});
printer->write("White", Style{Color::kWhite, true});
}
TEST_F(PrinterTest, WithoutColors) {
auto printer = Printer::create(stdout, false);
printer->write("Default", Style{Color::kDefault, false});
printer->write("Black", Style{Color::kBlack, false});
printer->write("Red", Style{Color::kRed, false});
printer->write("Green", Style{Color::kGreen, false});
printer->write("Yellow", Style{Color::kYellow, false});
printer->write("Blue", Style{Color::kBlue, false});
printer->write("Magenta", Style{Color::kMagenta, false});
printer->write("Cyan", Style{Color::kCyan, false});
printer->write("White", Style{Color::kWhite, false});
}
TEST_F(PrinterTest, BoldWithoutColors) {
auto printer = Printer::create(stdout, false);
printer->write("Default", Style{Color::kDefault, true});
printer->write("Black", Style{Color::kBlack, true});
printer->write("Red", Style{Color::kRed, true});
printer->write("Green", Style{Color::kGreen, true});
printer->write("Yellow", Style{Color::kYellow, true});
printer->write("Blue", Style{Color::kBlue, true});
printer->write("Magenta", Style{Color::kMagenta, true});
printer->write("Cyan", Style{Color::kCyan, true});
printer->write("White", Style{Color::kWhite, true});
}
} // namespace
} // namespace diag
} // namespace tint

View File

@ -0,0 +1,113 @@
// Copyright 2020 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/diagnostic/printer.h"
#include <cstring>
#define WIN32_LEAN_AND_MEAN 1
#include <Windows.h>
namespace tint {
namespace diag {
namespace {
struct ConsoleInfo {
HANDLE handle = INVALID_HANDLE_VALUE;
WORD default_attributes = 0;
operator bool() const { return handle != INVALID_HANDLE_VALUE; }
};
ConsoleInfo console_info(FILE* file) {
if (file == nullptr) {
return {};
}
ConsoleInfo console{};
if (file == stdout) {
console.handle = GetStdHandle(STD_OUTPUT_HANDLE);
} else if (file == stderr) {
console.handle = GetStdHandle(STD_ERROR_HANDLE);
} else {
return {};
}
CONSOLE_SCREEN_BUFFER_INFO info{};
if (GetConsoleScreenBufferInfo(console.handle, &info) == 0) {
return {};
}
console.default_attributes = info.wAttributes;
return console;
}
class PrinterWindows : public Printer {
public:
PrinterWindows(FILE* f, bool use_colors)
: file(f), console(console_info(use_colors ? f : nullptr)) {}
void write(const std::string& str, const Style& style) override {
write_color(style.color, style.bold);
fwrite(str.data(), 1, str.size(), file);
write_color(Color::kDefault, false);
}
private:
WORD attributes(Color color, bool bold) {
switch (color) {
case Color::kDefault:
return console.default_attributes;
case Color::kBlack:
return 0;
case Color::kRed:
return FOREGROUND_RED | (bold ? FOREGROUND_INTENSITY : 0);
case Color::kGreen:
return FOREGROUND_GREEN | (bold ? FOREGROUND_INTENSITY : 0);
case Color::kYellow:
return FOREGROUND_RED | FOREGROUND_GREEN |
(bold ? FOREGROUND_INTENSITY : 0);
case Color::kBlue:
return FOREGROUND_BLUE | (bold ? FOREGROUND_INTENSITY : 0);
case Color::kMagenta:
return FOREGROUND_RED | FOREGROUND_BLUE |
(bold ? FOREGROUND_INTENSITY : 0);
case Color::kCyan:
return FOREGROUND_GREEN | FOREGROUND_BLUE |
(bold ? FOREGROUND_INTENSITY : 0);
case Color::kWhite:
return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE |
(bold ? FOREGROUND_INTENSITY : 0);
}
return 0; // unreachable
}
void write_color(Color color, bool bold) {
if (console) {
SetConsoleTextAttribute(console.handle, attributes(color, bold));
fflush(file);
}
}
FILE* const file;
ConsoleInfo const console;
};
} // namespace
std::unique_ptr<Printer> Printer::create(FILE* out, bool use_colors) {
return std::make_unique<PrinterWindows>(out, use_colors);
}
} // namespace diag
} // namespace tint