Make Formatter a non-interface

I had originally created `Formatter` as an interface as I was intending to implement this differently for linux and windows (for terminal coloring).

Color printing is instead implemented by the `Printer` interface / PIMPL classes.

Replace the multi-boolean constructor with a `Style` struct, as this will make life easier when we want to add / remove flags.

Bug: tint:282
Change-Id: I630073ed7a76c023348b66e8a8517b00b2b6a0d2
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/31569
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
This commit is contained in:
Ben Clayton 2020-11-02 21:16:38 +00:00 committed by Commit Bot service account
parent 28f7764704
commit ecea5c8aec
5 changed files with 184 additions and 177 deletions

View File

@ -18,6 +18,7 @@
#include <sstream> #include <sstream>
#include "src/diagnostic/diagnostic.h" #include "src/diagnostic/diagnostic.h"
#include "src/diagnostic/printer.h"
namespace tint { namespace tint {
namespace diag { namespace diag {
@ -57,22 +58,26 @@ std::basic_ostream<CharT, Traits>& operator<<(
return stream; return stream;
} }
class BasicFormatter : public Formatter { } // namespace
public:
struct State { /// 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) {} explicit State(Printer* p) : printer(p) {}
~State() { flush(); } ~State() { flush(); }
// set_style() sets the current style to new_style, flushing any pending /// set_style() sets the current style to new_style, flushing any pending
// messages to the printer if the style changed. /// messages to the printer if the style changed.
void set_style(const Style& new_style) { /// @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) { if (style.color != new_style.color || style.bold != new_style.bold) {
flush(); flush();
style = new_style; style = new_style;
} }
} }
// flush() writes any pending messages to the printer, clearing the buffer. /// flush writes any pending messages to the printer, clearing the buffer.
void flush() { void flush() {
auto str = stream.str(); auto str = stream.str();
if (str.length() > 0) { if (str.length() > 0) {
@ -82,17 +87,21 @@ class BasicFormatter : public Formatter {
} }
} }
// operator<<() queues msg to be written to the printer. /// 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> template <typename T>
State& operator<<(const T& msg) { State& operator<<(const T& msg) {
stream << msg; stream << msg;
return *this; return *this;
} }
// newline() queues a newline to be written to the printer. /// newline queues a newline to be written to the printer.
void newline() { stream << std::endl; } void newline() { stream << std::endl; }
// repeat() queues the character c to be writen to the printer n times. /// repeat queues the character c to be writen 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) { void repeat(char c, size_t n) {
while (n-- > 0) { while (n-- > 0) {
stream << c; stream << c;
@ -101,16 +110,14 @@ class BasicFormatter : public Formatter {
private: private:
Printer* printer; Printer* printer;
Style style; diag::Style style;
std::stringstream stream; std::stringstream stream;
}; };
BasicFormatter(bool print_file, bool print_severity, bool print_line) Formatter::Formatter() {}
: print_file_(print_file), Formatter::Formatter(const Style& style) : style_(style) {}
print_severity_(print_severity),
print_line_(print_line) {}
void format(const List& list, Printer* printer) const override { void Formatter::format(const List& list, Printer* printer) const {
State state{printer}; State state{printer};
bool first = true; bool first = true;
@ -119,92 +126,84 @@ class BasicFormatter : public Formatter {
if (!first) { if (!first) {
state.newline(); state.newline();
} }
format(diag, &state); format(diag, state);
first = false; first = false;
} }
} }
private: void Formatter::format(const Diagnostic& diag, State& state) 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}); state.set_style({Color::kDefault, true});
if (print_file_ && src.file != nullptr && !src.file->path.empty()) { if (style_.print_file && src.file != nullptr && !src.file->path.empty()) {
(*state) << src.file->path; state << src.file->path;
if (rng.begin.line > 0) { if (rng.begin.line > 0) {
(*state) << ":" << rng.begin; state << ":" << rng.begin;
} }
} else { } else {
(*state) << rng.begin; state << rng.begin;
} }
if (print_severity_) { if (style_.print_severity) {
switch (diag.severity) { switch (diag.severity) {
case Severity::Warning: case Severity::Warning:
state->set_style({Color::kYellow, true}); state.set_style({Color::kYellow, true});
break; break;
case Severity::Error: case Severity::Error:
case Severity::Fatal: case Severity::Fatal:
state->set_style({Color::kRed, true}); state.set_style({Color::kRed, true});
break; break;
default: default:
break; break;
} }
(*state) << " " << diag.severity; state << " " << diag.severity;
} }
state->set_style({Color::kDefault, true}); state.set_style({Color::kDefault, true});
(*state) << ": " << diag.message; state << ": " << diag.message;
if (print_line_ && src.file != nullptr && rng.begin.line > 0) { if (style_.print_line && src.file != nullptr && rng.begin.line > 0) {
state->newline(); state.newline();
state->set_style({Color::kDefault, false}); 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();
(*state) << src.file->lines[line - 1]; state << src.file->lines[line - 1];
state->newline();
state->set_style({Color::kCyan, false}); 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
state->repeat(' ', rng.begin.column - 1); state.repeat(' ', rng.begin.column - 1);
state->repeat( state.repeat('^',
'^', std::max<size_t>(rng.end.column - rng.begin.column, 1)); 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
state->repeat(' ', rng.begin.column - 1); state.repeat(' ', rng.begin.column - 1);
state->repeat('^', len - (rng.begin.column - 1)); 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
state->repeat('^', rng.end.column - 1); state.repeat('^', rng.end.column - 1);
} else { } else {
// Middle of multi-line // Middle of multi-line
state->repeat('^', len); state.repeat('^', len);
} }
state->newline(); state.newline();
} }
} }
state->set_style({}); state.set_style({});
}
} }
}
const bool print_file_ = false; std::string Formatter::format(const List& list) const {
const bool print_severity_ = false; StringPrinter printer;
const bool print_line_ = false; format(list, &printer);
}; return printer.str();
} // namespace
std::unique_ptr<Formatter> Formatter::create(bool print_file,
bool print_severity,
bool print_line) {
return std::make_unique<BasicFormatter>(print_file, print_severity,
print_line);
} }
Formatter::~Formatter() = default; Formatter::~Formatter() = default;

View File

@ -18,39 +18,49 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include "src/diagnostic/printer.h"
namespace tint { namespace tint {
namespace diag { namespace diag {
class Diagnostic;
class List; class List;
class Printer;
/// Formatters are used to format a list of diagnostics into a human readable /// Formatter are used to print a list of diagnostics messages.
/// string.
class Formatter { class Formatter {
public: public:
/// @returns a basic diagnostic formatter /// Style controls the formatter's output style.
/// @param print_file include the file path for each diagnostic struct Style {
/// @param print_severity include the severity for each diagnostic /// include the file path for each diagnostic
/// @param print_line include the source line(s) for the diagnostic bool print_file = true;
static std::unique_ptr<Formatter> create(bool print_file, /// include the severity for each diagnostic
bool print_severity, bool print_severity = true;
bool print_line); /// include the source line(s) for the diagnostic
bool print_line = true;
};
virtual ~Formatter(); /// 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();
/// format prints the formatted diagnostic list |list| to |printer|.
/// @param list the list of diagnostic messages to format /// @param list the list of diagnostic messages to format
/// @param printer the printer used to display the formatted diagnostics /// @param printer the printer used to display the formatted diagnostics
virtual void format(const List& list, Printer* printer) const = 0; void format(const List& list, Printer* printer) const;
/// @return the list of diagnostics |list| formatted to a string. /// @return the list of diagnostics |list| formatted to a string.
/// @param list the list of diagnostic messages to format /// @param list the list of diagnostic messages to format
inline std::string format(const List& list) const { std::string format(const List& list) const;
StringPrinter printer;
format(list, &printer); private:
return printer.str(); struct State;
}
void format(const Diagnostic& diag, State& state) const;
Style const style_;
}; };
} // namespace diag } // namespace diag

View File

@ -44,8 +44,8 @@ class DiagFormatterTest : public testing::Test {
}; };
TEST_F(DiagFormatterTest, Simple) { TEST_F(DiagFormatterTest, Simple) {
auto fmt = Formatter::create(false, false, false); Formatter fmt{{false, false, false}};
auto got = fmt->format(List{diag_info, diag_warn, diag_err, diag_fatal}); auto got = fmt.format(List{diag_info, diag_warn, diag_err, diag_fatal});
auto* expect = R"(1:14: purr auto* expect = R"(1:14: purr
2:14: grrr 2:14: grrr
3:16: hiss 3:16: hiss
@ -54,8 +54,8 @@ TEST_F(DiagFormatterTest, Simple) {
} }
TEST_F(DiagFormatterTest, WithFile) { TEST_F(DiagFormatterTest, WithFile) {
auto fmt = Formatter::create(true, false, false); Formatter fmt{{true, false, false}};
auto got = fmt->format(List{diag_info, diag_warn, diag_err, diag_fatal}); auto got = fmt.format(List{diag_info, diag_warn, diag_err, diag_fatal});
auto* expect = R"(file.name:1:14: purr auto* expect = R"(file.name:1:14: purr
file.name:2:14: grrr file.name:2:14: grrr
file.name:3:16: hiss file.name:3:16: hiss
@ -64,8 +64,8 @@ file.name:4:16: nothing)";
} }
TEST_F(DiagFormatterTest, WithSeverity) { TEST_F(DiagFormatterTest, WithSeverity) {
auto fmt = Formatter::create(false, true, false); Formatter fmt{{false, true, false}};
auto got = fmt->format(List{diag_info, diag_warn, diag_err, diag_fatal}); auto got = fmt.format(List{diag_info, diag_warn, diag_err, diag_fatal});
auto* expect = R"(1:14 info: purr auto* expect = R"(1:14 info: purr
2:14 warning: grrr 2:14 warning: grrr
3:16 error: hiss 3:16 error: hiss
@ -74,8 +74,8 @@ TEST_F(DiagFormatterTest, WithSeverity) {
} }
TEST_F(DiagFormatterTest, WithLine) { TEST_F(DiagFormatterTest, WithLine) {
auto fmt = Formatter::create(false, false, true); Formatter fmt{{false, false, true}};
auto got = fmt->format(List{diag_info, diag_warn, diag_err, diag_fatal}); auto got = fmt.format(List{diag_info, diag_warn, diag_err, diag_fatal});
auto* expect = R"(1:14: purr auto* expect = R"(1:14: purr
the cat says meow the cat says meow
^ ^
@ -96,8 +96,8 @@ the snail says ???
} }
TEST_F(DiagFormatterTest, BasicWithFileSeverityLine) { TEST_F(DiagFormatterTest, BasicWithFileSeverityLine) {
auto fmt = Formatter::create(true, true, true); Formatter fmt{{true, true, true}};
auto got = fmt->format(List{diag_info, diag_warn, diag_err, diag_fatal}); auto got = fmt.format(List{diag_info, diag_warn, diag_err, diag_fatal});
auto* expect = R"(file.name:1:14 info: purr auto* expect = R"(file.name:1:14 info: purr
the cat says meow the cat says meow
^ ^
@ -121,9 +121,8 @@ TEST_F(DiagFormatterTest, BasicWithMultiLine) {
Diagnostic multiline{Severity::Warning, Diagnostic multiline{Severity::Warning,
Source{Source::Range{{2, 9}, {4, 15}}, &file}, Source{Source::Range{{2, 9}, {4, 15}}, &file},
"multiline"}; "multiline"};
Formatter fmt{{false, false, true}};
auto fmt = Formatter::create(false, false, true); auto got = fmt.format(List{multiline});
auto got = fmt->format(List{multiline});
auto* expect = R"(2:9: multiline auto* expect = R"(2:9: multiline
the dog says woof the dog says woof
^^^^^^^^^ ^^^^^^^^^

View File

@ -104,7 +104,8 @@ class ParserImpl {
/// @returns the parser error string /// @returns the parser error string
std::string error() const { std::string error() const {
return diag::Formatter::create(false, false, false)->format(diags_); diag::Formatter formatter{{false, false, false}};
return formatter.format(diags_);
} }
/// @returns the diagnostic messages /// @returns the diagnostic messages

View File

@ -30,9 +30,7 @@ class ParserImplErrorTest : public ParserImplTest {};
auto* p = parser(source); \ auto* p = parser(source); \
EXPECT_EQ(false, p->Parse()); \ EXPECT_EQ(false, p->Parse()); \
EXPECT_EQ(true, p->diagnostics().contains_errors()); \ EXPECT_EQ(true, p->diagnostics().contains_errors()); \
EXPECT_EQ( \ EXPECT_EQ(expected, diag::Formatter().format(p->diagnostics())); \
expected, \
diag::Formatter::create(true, true, true)->format(p->diagnostics())); \
} while (false) } while (false)
TEST_F(ParserImplErrorTest, AdditiveInvalidExpr) { TEST_F(ParserImplErrorTest, AdditiveInvalidExpr) {