mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-12-16 00:17:03 +00:00
tint->dawn: Shuffle source tree in preperation of merging repos
docs/ -> docs/tint/ fuzzers/ -> src/tint/fuzzers/ samples/ -> src/tint/cmd/ src/ -> src/tint/ test/ -> test/tint/ BUG=tint:1418,tint:1433 Change-Id: Id2aa79f989aef3245b80ef4aa37a27ff16cd700b Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/80482 Kokoro: Kokoro <noreply+kokoro@google.com> Reviewed-by: Ben Clayton <bclayton@google.com> Commit-Queue: Ryan Harrison <rharrison@chromium.org>
This commit is contained in:
committed by
Tint LUCI CQ
parent
38f1e9c75c
commit
dbc13af287
48
src/tint/diagnostic/diagnostic.cc
Normal file
48
src/tint/diagnostic/diagnostic.cc
Normal file
@@ -0,0 +1,48 @@
|
||||
// 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/tint/diagnostic/diagnostic.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "src/tint/diagnostic/formatter.h"
|
||||
|
||||
namespace tint {
|
||||
namespace diag {
|
||||
|
||||
Diagnostic::Diagnostic() = default;
|
||||
Diagnostic::Diagnostic(const Diagnostic&) = default;
|
||||
Diagnostic::~Diagnostic() = default;
|
||||
Diagnostic& Diagnostic::operator=(const Diagnostic&) = default;
|
||||
|
||||
List::List() = default;
|
||||
List::List(std::initializer_list<Diagnostic> list) : entries_(list) {}
|
||||
List::List(const List& rhs) = default;
|
||||
|
||||
List::List(List&& rhs) = default;
|
||||
|
||||
List::~List() = default;
|
||||
|
||||
List& List::operator=(const List& rhs) = default;
|
||||
|
||||
List& List::operator=(List&& rhs) = default;
|
||||
|
||||
std::string List::str() const {
|
||||
diag::Formatter::Style style;
|
||||
style.print_newline_at_end = false;
|
||||
return Formatter{style}.format(*this);
|
||||
}
|
||||
|
||||
} // namespace diag
|
||||
} // namespace tint
|
||||
252
src/tint/diagnostic/diagnostic.h
Normal file
252
src/tint/diagnostic/diagnostic.h
Normal file
@@ -0,0 +1,252 @@
|
||||
// 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_TINT_DIAGNOSTIC_DIAGNOSTIC_H_
|
||||
#define SRC_TINT_DIAGNOSTIC_DIAGNOSTIC_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "src/tint/source.h"
|
||||
|
||||
namespace tint {
|
||||
namespace diag {
|
||||
|
||||
/// Severity is an enumerator of diagnostic severities.
|
||||
enum class Severity { Note, Warning, Error, InternalCompilerError, Fatal };
|
||||
|
||||
/// @return true iff `a` is more than, or of equal severity to `b`
|
||||
inline bool operator>=(Severity a, Severity b) {
|
||||
return static_cast<int>(a) >= static_cast<int>(b);
|
||||
}
|
||||
|
||||
/// System is an enumerator of Tint systems that can be the originator of a
|
||||
/// diagnostic message.
|
||||
enum class System {
|
||||
AST,
|
||||
Clone,
|
||||
Inspector,
|
||||
Program,
|
||||
ProgramBuilder,
|
||||
Reader,
|
||||
Resolver,
|
||||
Semantic,
|
||||
Symbol,
|
||||
Test,
|
||||
Transform,
|
||||
Utils,
|
||||
Writer,
|
||||
};
|
||||
|
||||
/// Diagnostic holds all the information for a single compiler diagnostic
|
||||
/// message.
|
||||
class Diagnostic {
|
||||
public:
|
||||
/// Constructor
|
||||
Diagnostic();
|
||||
/// Copy constructor
|
||||
Diagnostic(const Diagnostic&);
|
||||
/// Destructor
|
||||
~Diagnostic();
|
||||
|
||||
/// Copy assignment operator
|
||||
/// @return this diagnostic
|
||||
Diagnostic& operator=(const Diagnostic&);
|
||||
|
||||
/// severity is the severity of the diagnostic message.
|
||||
Severity severity = Severity::Error;
|
||||
/// source is the location of the diagnostic.
|
||||
Source source;
|
||||
/// message is the text associated with the diagnostic.
|
||||
std::string message;
|
||||
/// system is the Tint system that raised the diagnostic.
|
||||
System system;
|
||||
/// code is the error code, for example a validation error might have the code
|
||||
/// `"v-0001"`.
|
||||
const char* code = nullptr;
|
||||
/// A shared pointer to a Source::File. Only used if the diagnostic Source
|
||||
/// points to a file that was created specifically for this diagnostic
|
||||
/// (usually an ICE).
|
||||
std::shared_ptr<Source::File> owned_file = nullptr;
|
||||
};
|
||||
|
||||
/// List is a container of Diagnostic messages.
|
||||
class List {
|
||||
public:
|
||||
/// iterator is the type used for range based iteration.
|
||||
using iterator = std::vector<Diagnostic>::const_iterator;
|
||||
|
||||
/// Constructs the list with no elements.
|
||||
List();
|
||||
|
||||
/// Copy constructor. Copies the diagnostics from `list` into this list.
|
||||
/// @param list the list of diagnostics to copy into this list.
|
||||
List(std::initializer_list<Diagnostic> list);
|
||||
|
||||
/// Copy constructor. Copies the diagnostics from `list` into this list.
|
||||
/// @param list the list of diagnostics to copy into this list.
|
||||
List(const List& list);
|
||||
|
||||
/// Move constructor. Moves the diagnostics from `list` into this list.
|
||||
/// @param list the list of diagnostics to move into this list.
|
||||
List(List&& list);
|
||||
|
||||
/// Destructor
|
||||
~List();
|
||||
|
||||
/// Assignment operator. Copies the diagnostics from `list` into this list.
|
||||
/// @param list the list to copy into this list.
|
||||
/// @return this list.
|
||||
List& operator=(const List& list);
|
||||
|
||||
/// Assignment move operator. Moves the diagnostics from `list` into this
|
||||
/// list.
|
||||
/// @param list the list to move into this list.
|
||||
/// @return this list.
|
||||
List& operator=(List&& list);
|
||||
|
||||
/// adds a diagnostic to the end of this list.
|
||||
/// @param diag the diagnostic to append to this list.
|
||||
void add(Diagnostic&& diag) {
|
||||
if (diag.severity >= Severity::Error) {
|
||||
error_count_++;
|
||||
}
|
||||
entries_.emplace_back(std::move(diag));
|
||||
}
|
||||
|
||||
/// adds a list of diagnostics to the end of this list.
|
||||
/// @param list the diagnostic to append to this list.
|
||||
void add(const List& list) {
|
||||
for (auto diag : list) {
|
||||
add(std::move(diag));
|
||||
}
|
||||
}
|
||||
|
||||
/// adds the note message with the given Source to the end of this list.
|
||||
/// @param system the system raising the note message
|
||||
/// @param note_msg the note message
|
||||
/// @param source the source of the note diagnostic
|
||||
void add_note(System system,
|
||||
const std::string& note_msg,
|
||||
const Source& source) {
|
||||
diag::Diagnostic note{};
|
||||
note.severity = diag::Severity::Note;
|
||||
note.system = system;
|
||||
note.source = source;
|
||||
note.message = note_msg;
|
||||
add(std::move(note));
|
||||
}
|
||||
|
||||
/// adds the warning message with the given Source to the end of this list.
|
||||
/// @param system the system raising the warning message
|
||||
/// @param warning_msg the warning message
|
||||
/// @param source the source of the warning diagnostic
|
||||
void add_warning(System system,
|
||||
const std::string& warning_msg,
|
||||
const Source& source) {
|
||||
diag::Diagnostic warning{};
|
||||
warning.severity = diag::Severity::Warning;
|
||||
warning.system = system;
|
||||
warning.source = source;
|
||||
warning.message = warning_msg;
|
||||
add(std::move(warning));
|
||||
}
|
||||
|
||||
/// adds the error message without a source to the end of this list.
|
||||
/// @param system the system raising the error message
|
||||
/// @param err_msg the error message
|
||||
void add_error(System system, std::string err_msg) {
|
||||
diag::Diagnostic error{};
|
||||
error.severity = diag::Severity::Error;
|
||||
error.system = system;
|
||||
error.message = std::move(err_msg);
|
||||
add(std::move(error));
|
||||
}
|
||||
|
||||
/// adds the error message with the given Source to the end of this list.
|
||||
/// @param system the system raising the error message
|
||||
/// @param err_msg the error message
|
||||
/// @param source the source of the error diagnostic
|
||||
void add_error(System system, std::string err_msg, const Source& source) {
|
||||
diag::Diagnostic error{};
|
||||
error.severity = diag::Severity::Error;
|
||||
error.system = system;
|
||||
error.source = source;
|
||||
error.message = std::move(err_msg);
|
||||
add(std::move(error));
|
||||
}
|
||||
|
||||
/// adds the error message with the given code and Source to the end of this
|
||||
/// list.
|
||||
/// @param system the system raising the error message
|
||||
/// @param code the error code
|
||||
/// @param err_msg the error message
|
||||
/// @param source the source of the error diagnostic
|
||||
void add_error(System system,
|
||||
const char* code,
|
||||
std::string err_msg,
|
||||
const Source& source) {
|
||||
diag::Diagnostic error{};
|
||||
error.code = code;
|
||||
error.severity = diag::Severity::Error;
|
||||
error.system = system;
|
||||
error.source = source;
|
||||
error.message = std::move(err_msg);
|
||||
add(std::move(error));
|
||||
}
|
||||
|
||||
/// adds an internal compiler error message to the end of this list.
|
||||
/// @param system the system raising the error message
|
||||
/// @param err_msg the error message
|
||||
/// @param source the source of the internal compiler error
|
||||
/// @param file the Source::File owned by this diagnostic
|
||||
void add_ice(System system,
|
||||
const std::string& err_msg,
|
||||
const Source& source,
|
||||
std::shared_ptr<Source::File> file) {
|
||||
diag::Diagnostic ice{};
|
||||
ice.severity = diag::Severity::InternalCompilerError;
|
||||
ice.system = system;
|
||||
ice.source = source;
|
||||
ice.message = err_msg;
|
||||
ice.owned_file = std::move(file);
|
||||
add(std::move(ice));
|
||||
}
|
||||
|
||||
/// @returns true iff the diagnostic list contains errors diagnostics (or of
|
||||
/// higher severity).
|
||||
bool contains_errors() const { return error_count_ > 0; }
|
||||
/// @returns the number of error diagnostics (or of higher severity).
|
||||
size_t error_count() const { return error_count_; }
|
||||
/// @returns the number of entries in the list.
|
||||
size_t count() const { return entries_.size(); }
|
||||
/// @returns the first diagnostic in the list.
|
||||
iterator begin() const { return entries_.begin(); }
|
||||
/// @returns the last diagnostic in the list.
|
||||
iterator end() const { return entries_.end(); }
|
||||
|
||||
/// @returns a formatted string of all the diagnostics in this list.
|
||||
std::string str() const;
|
||||
|
||||
private:
|
||||
std::vector<Diagnostic> entries_;
|
||||
size_t error_count_ = 0;
|
||||
};
|
||||
|
||||
} // namespace diag
|
||||
} // namespace tint
|
||||
|
||||
#endif // SRC_TINT_DIAGNOSTIC_DIAGNOSTIC_H_
|
||||
42
src/tint/diagnostic/diagnostic_test.cc
Normal file
42
src/tint/diagnostic/diagnostic_test.cc
Normal 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/tint/diagnostic/formatter.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "src/tint/diagnostic/diagnostic.h"
|
||||
|
||||
namespace tint {
|
||||
namespace diag {
|
||||
namespace {
|
||||
|
||||
TEST(DiagListTest, OwnedFilesShared) {
|
||||
auto file = std::make_shared<Source::File>("path", "content");
|
||||
|
||||
diag::List list_a, list_b;
|
||||
{
|
||||
diag::Diagnostic diag{};
|
||||
diag.source = Source{Source::Range{{0, 0}}, file.get()};
|
||||
list_a.add(std::move(diag));
|
||||
}
|
||||
|
||||
list_b = list_a;
|
||||
|
||||
ASSERT_EQ(list_b.count(), list_a.count());
|
||||
EXPECT_EQ(list_b.begin()->source.file, file.get());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace diag
|
||||
} // namespace tint
|
||||
271
src/tint/diagnostic/formatter.cc
Normal file
271
src/tint/diagnostic/formatter.cc
Normal file
@@ -0,0 +1,271 @@
|
||||
// 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/tint/diagnostic/formatter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
|
||||
#include "src/tint/diagnostic/diagnostic.h"
|
||||
#include "src/tint/diagnostic/printer.h"
|
||||
|
||||
namespace tint {
|
||||
namespace diag {
|
||||
namespace {
|
||||
|
||||
const char* to_str(Severity severity) {
|
||||
switch (severity) {
|
||||
case Severity::Note:
|
||||
return "note";
|
||||
case Severity::Warning:
|
||||
return "warning";
|
||||
case Severity::Error:
|
||||
return "error";
|
||||
case Severity::InternalCompilerError:
|
||||
return "internal compiler error";
|
||||
case Severity::Fatal:
|
||||
return "fatal";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string to_str(const Source::Location& location) {
|
||||
std::stringstream ss;
|
||||
if (location.line > 0) {
|
||||
ss << location.line;
|
||||
if (location.column > 0) {
|
||||
ss << ":" << location.column;
|
||||
}
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/// State holds the internal formatter state for a format() call.
|
||||
struct Formatter::State {
|
||||
/// Constructs a State associated with the given printer.
|
||||
/// @param p the printer to write formatted messages to.
|
||||
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.
|
||||
/// @param new_style the new style to apply for future written messages.
|
||||
void set_style(const diag::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.
|
||||
/// @param msg the value or string to write to the printer
|
||||
/// @returns this State so that calls can be chained
|
||||
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 written to the printer n times.
|
||||
/// @param c the character to print `n` times
|
||||
/// @param n the number of times to print character `c`
|
||||
void repeat(char c, size_t n) {
|
||||
std::fill_n(std::ostream_iterator<char>(stream), n, c);
|
||||
}
|
||||
|
||||
private:
|
||||
Printer* printer;
|
||||
diag::Style style;
|
||||
std::stringstream stream;
|
||||
};
|
||||
|
||||
Formatter::Formatter() {}
|
||||
Formatter::Formatter(const Style& style) : style_(style) {}
|
||||
|
||||
void Formatter::format(const List& list, Printer* printer) const {
|
||||
State state{printer};
|
||||
|
||||
bool first = true;
|
||||
for (auto diag : list) {
|
||||
state.set_style({});
|
||||
if (!first) {
|
||||
state.newline();
|
||||
}
|
||||
format(diag, state);
|
||||
first = false;
|
||||
}
|
||||
|
||||
if (style_.print_newline_at_end) {
|
||||
state.newline();
|
||||
}
|
||||
}
|
||||
|
||||
void Formatter::format(const Diagnostic& diag, State& state) const {
|
||||
auto const& src = diag.source;
|
||||
auto const& rng = src.range;
|
||||
bool has_code = diag.code != nullptr && diag.code[0] != '\0';
|
||||
|
||||
state.set_style({Color::kDefault, true});
|
||||
|
||||
struct TextAndColor {
|
||||
std::string text;
|
||||
Color color;
|
||||
bool bold = false;
|
||||
};
|
||||
std::vector<TextAndColor> prefix;
|
||||
prefix.reserve(6);
|
||||
|
||||
if (style_.print_file && src.file != nullptr) {
|
||||
if (rng.begin.line > 0) {
|
||||
prefix.emplace_back(TextAndColor{src.file->path + ":" + to_str(rng.begin),
|
||||
Color::kDefault});
|
||||
} else {
|
||||
prefix.emplace_back(TextAndColor{src.file->path, Color::kDefault});
|
||||
}
|
||||
} else if (rng.begin.line > 0) {
|
||||
prefix.emplace_back(TextAndColor{to_str(rng.begin), Color::kDefault});
|
||||
}
|
||||
|
||||
Color severity_color = Color::kDefault;
|
||||
switch (diag.severity) {
|
||||
case Severity::Note:
|
||||
break;
|
||||
case Severity::Warning:
|
||||
severity_color = Color::kYellow;
|
||||
break;
|
||||
case Severity::Error:
|
||||
severity_color = Color::kRed;
|
||||
break;
|
||||
case Severity::Fatal:
|
||||
case Severity::InternalCompilerError:
|
||||
severity_color = Color::kMagenta;
|
||||
break;
|
||||
}
|
||||
if (style_.print_severity) {
|
||||
prefix.emplace_back(
|
||||
TextAndColor{to_str(diag.severity), severity_color, true});
|
||||
}
|
||||
if (has_code) {
|
||||
prefix.emplace_back(TextAndColor{diag.code, severity_color});
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < prefix.size(); i++) {
|
||||
if (i > 0) {
|
||||
state << " ";
|
||||
}
|
||||
state.set_style({prefix[i].color, prefix[i].bold});
|
||||
state << prefix[i].text;
|
||||
}
|
||||
|
||||
state.set_style({Color::kDefault, true});
|
||||
if (!prefix.empty()) {
|
||||
state << ": ";
|
||||
}
|
||||
state << diag.message;
|
||||
|
||||
if (style_.print_line && src.file && rng.begin.line > 0) {
|
||||
state.newline();
|
||||
state.set_style({Color::kDefault, false});
|
||||
|
||||
for (size_t line_num = rng.begin.line;
|
||||
(line_num <= rng.end.line) &&
|
||||
(line_num <= src.file->content.lines.size());
|
||||
line_num++) {
|
||||
auto& line = src.file->content.lines[line_num - 1];
|
||||
auto line_len = line.size();
|
||||
|
||||
bool is_ascii = true;
|
||||
for (auto c : line) {
|
||||
if (c == '\t') {
|
||||
state.repeat(' ', style_.tab_width);
|
||||
} else {
|
||||
state << c;
|
||||
}
|
||||
if (c & 0x80) {
|
||||
is_ascii = false;
|
||||
}
|
||||
}
|
||||
|
||||
state.newline();
|
||||
|
||||
// If the line contains non-ascii characters, then we cannot assume that
|
||||
// a single utf8 code unit represents a single glyph, so don't attempt to
|
||||
// draw squiggles.
|
||||
if (!is_ascii) {
|
||||
continue;
|
||||
}
|
||||
|
||||
state.set_style({Color::kCyan, false});
|
||||
|
||||
// Count the number of glyphs in the line span.
|
||||
// start and end use 1-based indexing.
|
||||
auto num_glyphs = [&](size_t start, size_t end) {
|
||||
size_t count = 0;
|
||||
start = (start > 0) ? (start - 1) : 0;
|
||||
end = (end > 0) ? (end - 1) : 0;
|
||||
for (size_t i = start; (i < end) && (i < line_len); i++) {
|
||||
count += (line[i] == '\t') ? style_.tab_width : 1;
|
||||
}
|
||||
return count;
|
||||
};
|
||||
|
||||
if (line_num == rng.begin.line && line_num == rng.end.line) {
|
||||
// Single line
|
||||
state.repeat(' ', num_glyphs(1, rng.begin.column));
|
||||
state.repeat('^', std::max<size_t>(
|
||||
num_glyphs(rng.begin.column, rng.end.column), 1));
|
||||
} else if (line_num == rng.begin.line) {
|
||||
// Start of multi-line
|
||||
state.repeat(' ', num_glyphs(1, rng.begin.column));
|
||||
state.repeat('^', num_glyphs(rng.begin.column, line_len + 1));
|
||||
} else if (line_num == rng.end.line) {
|
||||
// End of multi-line
|
||||
state.repeat('^', num_glyphs(1, rng.end.column));
|
||||
} else {
|
||||
// Middle of multi-line
|
||||
state.repeat('^', num_glyphs(1, line_len + 1));
|
||||
}
|
||||
state.newline();
|
||||
}
|
||||
|
||||
state.set_style({});
|
||||
}
|
||||
}
|
||||
|
||||
std::string Formatter::format(const List& list) const {
|
||||
StringPrinter printer;
|
||||
format(list, &printer);
|
||||
return printer.str();
|
||||
}
|
||||
|
||||
Formatter::~Formatter() = default;
|
||||
|
||||
} // namespace diag
|
||||
} // namespace tint
|
||||
72
src/tint/diagnostic/formatter.h
Normal file
72
src/tint/diagnostic/formatter.h
Normal file
@@ -0,0 +1,72 @@
|
||||
// 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_TINT_DIAGNOSTIC_FORMATTER_H_
|
||||
#define SRC_TINT_DIAGNOSTIC_FORMATTER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace tint {
|
||||
namespace diag {
|
||||
|
||||
class Diagnostic;
|
||||
class List;
|
||||
class Printer;
|
||||
|
||||
/// Formatter are used to print a list of diagnostics messages.
|
||||
class Formatter {
|
||||
public:
|
||||
/// Style controls the formatter's output style.
|
||||
struct Style {
|
||||
/// include the file path for each diagnostic
|
||||
bool print_file = true;
|
||||
/// include the severity for each diagnostic
|
||||
bool print_severity = true;
|
||||
/// include the source line(s) for the diagnostic
|
||||
bool print_line = true;
|
||||
/// print a newline at the end of a diagnostic list
|
||||
bool print_newline_at_end = true;
|
||||
/// width of a tab character
|
||||
size_t tab_width = 2u;
|
||||
};
|
||||
|
||||
/// Constructor for the formatter using a default style.
|
||||
Formatter();
|
||||
|
||||
/// Constructor for the formatter using the custom style.
|
||||
/// @param style the style used for the formatter.
|
||||
explicit Formatter(const Style& style);
|
||||
|
||||
~Formatter();
|
||||
|
||||
/// @param list the list of diagnostic messages to format
|
||||
/// @param printer the printer used to display the formatted diagnostics
|
||||
void format(const List& list, Printer* printer) const;
|
||||
|
||||
/// @return the list of diagnostics `list` formatted to a string.
|
||||
/// @param list the list of diagnostic messages to format
|
||||
std::string format(const List& list) const;
|
||||
|
||||
private:
|
||||
struct State;
|
||||
|
||||
void format(const Diagnostic& diag, State& state) const;
|
||||
|
||||
const Style style_;
|
||||
};
|
||||
|
||||
} // namespace diag
|
||||
} // namespace tint
|
||||
|
||||
#endif // SRC_TINT_DIAGNOSTIC_FORMATTER_H_
|
||||
310
src/tint/diagnostic/formatter_test.cc
Normal file
310
src/tint/diagnostic/formatter_test.cc
Normal file
@@ -0,0 +1,310 @@
|
||||
// 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/tint/diagnostic/formatter.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "src/tint/diagnostic/diagnostic.h"
|
||||
|
||||
namespace tint {
|
||||
namespace diag {
|
||||
namespace {
|
||||
|
||||
Diagnostic Diag(Severity severity,
|
||||
Source source,
|
||||
std::string message,
|
||||
System system,
|
||||
const char* code = nullptr) {
|
||||
Diagnostic d;
|
||||
d.severity = severity;
|
||||
d.source = source;
|
||||
d.message = std::move(message);
|
||||
d.system = system;
|
||||
d.code = code;
|
||||
return d;
|
||||
}
|
||||
|
||||
constexpr const char* ascii_content = // Note: words are tab-delimited
|
||||
R"(the cat says meow
|
||||
the dog says woof
|
||||
the snake says quack
|
||||
the snail says ???
|
||||
)";
|
||||
|
||||
constexpr const char* utf8_content = // Note: words are tab-delimited
|
||||
"the \xf0\x9f\x90\xb1 says meow\n" // NOLINT: tabs
|
||||
"the \xf0\x9f\x90\x95 says woof\n" // NOLINT: tabs
|
||||
"the \xf0\x9f\x90\x8d says quack\n" // NOLINT: tabs
|
||||
"the \xf0\x9f\x90\x8c says ???\n"; // NOLINT: tabs
|
||||
|
||||
class DiagFormatterTest : public testing::Test {
|
||||
public:
|
||||
Source::File ascii_file{"file.name", ascii_content};
|
||||
Source::File utf8_file{"file.name", utf8_content};
|
||||
Diagnostic ascii_diag_note =
|
||||
Diag(Severity::Note,
|
||||
Source{Source::Range{Source::Location{1, 14}}, &ascii_file},
|
||||
"purr",
|
||||
System::Test);
|
||||
Diagnostic ascii_diag_warn =
|
||||
Diag(Severity::Warning,
|
||||
Source{Source::Range{{2, 14}, {2, 18}}, &ascii_file},
|
||||
"grrr",
|
||||
System::Test);
|
||||
Diagnostic ascii_diag_err =
|
||||
Diag(Severity::Error,
|
||||
Source{Source::Range{{3, 16}, {3, 21}}, &ascii_file},
|
||||
"hiss",
|
||||
System::Test,
|
||||
"abc123");
|
||||
Diagnostic ascii_diag_ice =
|
||||
Diag(Severity::InternalCompilerError,
|
||||
Source{Source::Range{{4, 16}, {4, 19}}, &ascii_file},
|
||||
"unreachable",
|
||||
System::Test);
|
||||
Diagnostic ascii_diag_fatal =
|
||||
Diag(Severity::Fatal,
|
||||
Source{Source::Range{{4, 16}, {4, 19}}, &ascii_file},
|
||||
"nothing",
|
||||
System::Test);
|
||||
|
||||
Diagnostic utf8_diag_note =
|
||||
Diag(Severity::Note,
|
||||
Source{Source::Range{Source::Location{1, 15}}, &utf8_file},
|
||||
"purr",
|
||||
System::Test);
|
||||
Diagnostic utf8_diag_warn =
|
||||
Diag(Severity::Warning,
|
||||
Source{Source::Range{{2, 15}, {2, 19}}, &utf8_file},
|
||||
"grrr",
|
||||
System::Test);
|
||||
Diagnostic utf8_diag_err =
|
||||
Diag(Severity::Error,
|
||||
Source{Source::Range{{3, 15}, {3, 20}}, &utf8_file},
|
||||
"hiss",
|
||||
System::Test,
|
||||
"abc123");
|
||||
Diagnostic utf8_diag_ice =
|
||||
Diag(Severity::InternalCompilerError,
|
||||
Source{Source::Range{{4, 15}, {4, 18}}, &utf8_file},
|
||||
"unreachable",
|
||||
System::Test);
|
||||
Diagnostic utf8_diag_fatal =
|
||||
Diag(Severity::Fatal,
|
||||
Source{Source::Range{{4, 15}, {4, 18}}, &utf8_file},
|
||||
"nothing",
|
||||
System::Test);
|
||||
};
|
||||
|
||||
TEST_F(DiagFormatterTest, Simple) {
|
||||
Formatter fmt{{false, false, false, false}};
|
||||
auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
|
||||
auto* expect = R"(1:14: purr
|
||||
2:14: grrr
|
||||
3:16 abc123: hiss)";
|
||||
ASSERT_EQ(expect, got);
|
||||
}
|
||||
|
||||
TEST_F(DiagFormatterTest, SimpleNewlineAtEnd) {
|
||||
Formatter fmt{{false, false, false, true}};
|
||||
auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
|
||||
auto* expect = R"(1:14: purr
|
||||
2:14: grrr
|
||||
3:16 abc123: hiss
|
||||
)";
|
||||
ASSERT_EQ(expect, got);
|
||||
}
|
||||
|
||||
TEST_F(DiagFormatterTest, SimpleNoSource) {
|
||||
Formatter fmt{{false, false, false, false}};
|
||||
auto diag = Diag(Severity::Note, Source{}, "no source!", System::Test);
|
||||
auto got = fmt.format(List{diag});
|
||||
auto* expect = "no source!";
|
||||
ASSERT_EQ(expect, got);
|
||||
}
|
||||
|
||||
TEST_F(DiagFormatterTest, WithFile) {
|
||||
Formatter fmt{{true, false, false, false}};
|
||||
auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
|
||||
auto* expect = R"(file.name:1:14: purr
|
||||
file.name:2:14: grrr
|
||||
file.name:3:16 abc123: hiss)";
|
||||
ASSERT_EQ(expect, got);
|
||||
}
|
||||
|
||||
TEST_F(DiagFormatterTest, WithSeverity) {
|
||||
Formatter fmt{{false, true, false, false}};
|
||||
auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
|
||||
auto* expect = R"(1:14 note: purr
|
||||
2:14 warning: grrr
|
||||
3:16 error abc123: hiss)";
|
||||
ASSERT_EQ(expect, got);
|
||||
}
|
||||
|
||||
TEST_F(DiagFormatterTest, WithLine) {
|
||||
Formatter fmt{{false, false, true, false}};
|
||||
auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
|
||||
auto* expect = R"(1:14: purr
|
||||
the cat says meow
|
||||
^
|
||||
|
||||
2:14: grrr
|
||||
the dog says woof
|
||||
^^^^
|
||||
|
||||
3:16 abc123: hiss
|
||||
the snake says quack
|
||||
^^^^^
|
||||
)";
|
||||
ASSERT_EQ(expect, got);
|
||||
}
|
||||
|
||||
TEST_F(DiagFormatterTest, UnicodeWithLine) {
|
||||
Formatter fmt{{false, false, true, false}};
|
||||
auto got = fmt.format(List{utf8_diag_note, utf8_diag_warn, utf8_diag_err});
|
||||
auto* expect =
|
||||
"1:15: purr\n"
|
||||
"the \xf0\x9f\x90\xb1 says meow\n"
|
||||
"\n"
|
||||
"2:15: grrr\n"
|
||||
"the \xf0\x9f\x90\x95 says woof\n"
|
||||
"\n"
|
||||
"3:15 abc123: hiss\n"
|
||||
"the \xf0\x9f\x90\x8d says quack\n";
|
||||
ASSERT_EQ(expect, got);
|
||||
}
|
||||
|
||||
TEST_F(DiagFormatterTest, BasicWithFileSeverityLine) {
|
||||
Formatter fmt{{true, true, true, false}};
|
||||
auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
|
||||
auto* expect = R"(file.name:1:14 note: purr
|
||||
the cat says meow
|
||||
^
|
||||
|
||||
file.name:2:14 warning: grrr
|
||||
the dog says woof
|
||||
^^^^
|
||||
|
||||
file.name:3:16 error abc123: hiss
|
||||
the snake says quack
|
||||
^^^^^
|
||||
)";
|
||||
ASSERT_EQ(expect, got);
|
||||
}
|
||||
|
||||
TEST_F(DiagFormatterTest, BasicWithMultiLine) {
|
||||
auto multiline = Diag(Severity::Warning,
|
||||
Source{Source::Range{{2, 9}, {4, 15}}, &ascii_file},
|
||||
"multiline", System::Test);
|
||||
Formatter fmt{{false, false, true, false}};
|
||||
auto got = fmt.format(List{multiline});
|
||||
auto* expect = R"(2:9: multiline
|
||||
the dog says woof
|
||||
^^^^^^^^^^
|
||||
the snake says quack
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
the snail says ???
|
||||
^^^^^^^^^^^^^^^^
|
||||
)";
|
||||
ASSERT_EQ(expect, got);
|
||||
}
|
||||
|
||||
TEST_F(DiagFormatterTest, UnicodeWithMultiLine) {
|
||||
auto multiline = Diag(Severity::Warning,
|
||||
Source{Source::Range{{2, 9}, {4, 15}}, &utf8_file},
|
||||
"multiline", System::Test);
|
||||
Formatter fmt{{false, false, true, false}};
|
||||
auto got = fmt.format(List{multiline});
|
||||
auto* expect =
|
||||
"2:9: multiline\n"
|
||||
"the \xf0\x9f\x90\x95 says woof\n"
|
||||
"the \xf0\x9f\x90\x8d says quack\n"
|
||||
"the \xf0\x9f\x90\x8c says ???\n";
|
||||
ASSERT_EQ(expect, got);
|
||||
}
|
||||
|
||||
TEST_F(DiagFormatterTest, BasicWithFileSeverityLineTab4) {
|
||||
Formatter fmt{{true, true, true, false, 4u}};
|
||||
auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
|
||||
auto* expect = R"(file.name:1:14 note: purr
|
||||
the cat says meow
|
||||
^
|
||||
|
||||
file.name:2:14 warning: grrr
|
||||
the dog says woof
|
||||
^^^^
|
||||
|
||||
file.name:3:16 error abc123: hiss
|
||||
the snake says quack
|
||||
^^^^^
|
||||
)";
|
||||
ASSERT_EQ(expect, got);
|
||||
}
|
||||
|
||||
TEST_F(DiagFormatterTest, BasicWithMultiLineTab4) {
|
||||
auto multiline = Diag(Severity::Warning,
|
||||
Source{Source::Range{{2, 9}, {4, 15}}, &ascii_file},
|
||||
"multiline", System::Test);
|
||||
Formatter fmt{{false, false, true, false, 4u}};
|
||||
auto got = fmt.format(List{multiline});
|
||||
auto* expect = R"(2:9: multiline
|
||||
the dog says woof
|
||||
^^^^^^^^^^^^
|
||||
the snake says quack
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
the snail says ???
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
)";
|
||||
ASSERT_EQ(expect, got);
|
||||
}
|
||||
|
||||
TEST_F(DiagFormatterTest, ICE) {
|
||||
Formatter fmt{{}};
|
||||
auto got = fmt.format(List{ascii_diag_ice});
|
||||
auto* expect = R"(file.name:4:16 internal compiler error: unreachable
|
||||
the snail says ???
|
||||
^^^
|
||||
|
||||
)";
|
||||
ASSERT_EQ(expect, got);
|
||||
}
|
||||
|
||||
TEST_F(DiagFormatterTest, Fatal) {
|
||||
Formatter fmt{{}};
|
||||
auto got = fmt.format(List{ascii_diag_fatal});
|
||||
auto* expect = R"(file.name:4:16 fatal: nothing
|
||||
the snail says ???
|
||||
^^^
|
||||
|
||||
)";
|
||||
ASSERT_EQ(expect, got);
|
||||
}
|
||||
|
||||
TEST_F(DiagFormatterTest, RangeOOB) {
|
||||
Formatter fmt{{true, true, true, true}};
|
||||
diag::List list;
|
||||
list.add_error(System::Test, "oob",
|
||||
Source{{{10, 20}, {30, 20}}, &ascii_file});
|
||||
auto got = fmt.format(list);
|
||||
auto* expect = R"(file.name:10:20 error: oob
|
||||
|
||||
)";
|
||||
ASSERT_EQ(expect, got);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace diag
|
||||
} // namespace tint
|
||||
34
src/tint/diagnostic/printer.cc
Normal file
34
src/tint/diagnostic/printer.cc
Normal file
@@ -0,0 +1,34 @@
|
||||
// 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/tint/diagnostic/printer.h"
|
||||
|
||||
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/tint/diagnostic/printer.h
Normal file
83
src/tint/diagnostic/printer.h
Normal 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_TINT_DIAGNOSTIC_PRINTER_H_
|
||||
#define SRC_TINT_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_TINT_DIAGNOSTIC_PRINTER_H_
|
||||
100
src/tint/diagnostic/printer_linux.cc
Normal file
100
src/tint/diagnostic/printer_linux.cc
Normal 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 <unistd.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "src/tint/diagnostic/printer.h"
|
||||
|
||||
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;
|
||||
const bool 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
|
||||
42
src/tint/diagnostic/printer_other.cc
Normal file
42
src/tint/diagnostic/printer_other.cc
Normal 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 <cstring>
|
||||
|
||||
#include "src/tint/diagnostic/printer.h"
|
||||
|
||||
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
|
||||
98
src/tint/diagnostic/printer_test.cc
Normal file
98
src/tint/diagnostic/printer_test.cc
Normal file
@@ -0,0 +1,98 @@
|
||||
// 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/tint/diagnostic/printer.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace tint {
|
||||
namespace diag {
|
||||
namespace {
|
||||
|
||||
// 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.
|
||||
// This can be enabled or disabled with ENABLE_PRINTER_TESTS
|
||||
#define ENABLE_PRINTER_TESTS 0
|
||||
#if ENABLE_PRINTER_TESTS
|
||||
|
||||
using PrinterTest = testing::Test;
|
||||
|
||||
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});
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
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});
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
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});
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
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});
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
#endif // ENABLE_PRINTER_TESTS
|
||||
} // namespace
|
||||
} // namespace diag
|
||||
} // namespace tint
|
||||
113
src/tint/diagnostic/printer_windows.cc
Normal file
113
src/tint/diagnostic/printer_windows.cc
Normal 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 <cstring>
|
||||
|
||||
#include "src/tint/diagnostic/printer.h"
|
||||
|
||||
#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;
|
||||
const ConsoleInfo 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
|
||||
Reference in New Issue
Block a user