#include "config.h"
#define INCLUDE_MAP
+#define INCLUDE_STRING
#define INCLUDE_VECTOR
#include "system.h"
#include "coretypes.h"
#include "pretty-print-urlifier.h"
#include "edit-context.h"
#include "intl.h"
+#include "xml.h"
+#include "xml-printer.h"
+#include "json.h"
+
+// struct html_generation_options
+
+html_generation_options::html_generation_options ()
+: m_css (true),
+ m_javascript (true)
+{
+}
namespace xml {
# pragma GCC diagnostic ignored "-Wformat-diag"
#endif
-struct node
-{
- virtual ~node () {}
- virtual void write_as_xml (pretty_printer *pp,
- int depth, bool indent) const = 0;
- void dump (FILE *out) const;
- void DEBUG_FUNCTION dump () const { dump (stderr); }
-};
-
-struct text : public node
-{
- text (label_text str)
- : m_str (std::move (str))
- {}
-
- void write_as_xml (pretty_printer *pp,
- int depth, bool indent) const final override;
-
- label_text m_str;
-};
-
-struct node_with_children : public node
-{
- void add_child (std::unique_ptr<node> node);
- void add_text (label_text str);
-
- std::vector<std::unique_ptr<node>> m_children;
-};
-
-struct document : public node_with_children
-{
- void write_as_xml (pretty_printer *pp,
- int depth, bool indent) const final override;
-};
-
-struct element : public node_with_children
-{
- element (const char *kind, bool preserve_whitespace)
- : m_kind (kind),
- m_preserve_whitespace (preserve_whitespace)
- {}
-
- void write_as_xml (pretty_printer *pp,
- int depth, bool indent) const final override;
-
- void set_attr (const char *name, label_text value);
-
- const char *m_kind;
- bool m_preserve_whitespace;
- std::map<const char *, label_text> m_attributes;
-};
/* Implementation. */
/* struct text : public node. */
void
-text::write_as_xml (pretty_printer *pp, int /*depth*/, bool /*indent*/) const
+text::write_as_xml (pretty_printer *pp, int depth, bool indent) const
{
- write_escaped_text (pp, m_str.get ());
+ if (indent)
+ {
+ for (int i = 0; i < depth; ++i)
+ pp_string (pp, " ");
+ }
+ write_escaped_text (pp, m_str.c_str ());
+ if (indent)
+ pp_newline (pp);
}
/* struct node_with_children : public node. */
}
void
-node_with_children::add_text (label_text str)
+node_with_children::add_text (std::string str)
{
- gcc_assert (str.get ());
+ // Consolidate runs of text
+ if (!m_children.empty ())
+ if (text *t = m_children.back ()->dyn_cast_text ())
+ {
+ t->m_str += std::move (str);
+ return;
+ }
add_child (std::make_unique <text> (std::move (str)));
}
pp_string (pp, "<!DOCTYPE html\n"
" PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n"
" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">");
+ if (indent)
+ pp_newline (pp);
for (auto &iter : m_children)
iter->write_as_xml (pp, depth, indent);
}
{
if (indent)
{
- pp_newline (pp);
for (int i = 0; i < depth; ++i)
pp_string (pp, " ");
}
- if (m_preserve_whitespace)
- indent = false;
-
- pp_printf (pp, "<%s", m_kind);
- for (auto &attr : m_attributes)
+ pp_printf (pp, "<%s", m_kind.c_str ());
+ for (auto &key : m_key_insertion_order)
{
- pp_printf (pp, " %s=\"", attr.first);
- write_escaped_text (pp, attr.second.get ());
- pp_string (pp, "\"");
+ auto iter = m_attributes.find (key);
+ if (iter != m_attributes.end ())
+ {
+ pp_printf (pp, " %s=\"", key.c_str ());
+ write_escaped_text (pp, iter->second.c_str ());
+ pp_string (pp, "\"");
+ }
}
if (m_children.empty ())
- pp_string (pp, " />");
+ pp_string (pp, "/>");
else
{
+ const bool indent_children = m_preserve_whitespace ? false : indent;
pp_string (pp, ">");
+ if (indent_children)
+ pp_newline (pp);
for (auto &child : m_children)
- child->write_as_xml (pp, depth + 1, indent);
- if (indent)
+ child->write_as_xml (pp, depth + 1, indent_children);
+ if (indent_children)
{
- pp_newline (pp);
for (int i = 0; i < depth; ++i)
pp_string (pp, " ");
}
- pp_printf (pp, "</%s>", m_kind);
+ pp_printf (pp, "</%s>", m_kind.c_str ());
}
+
+ if (indent)
+ pp_newline (pp);
}
void
-element::set_attr (const char *name, label_text value)
+element::set_attr (const char *name, std::string value)
{
+ auto iter = m_attributes.find (name);
+ if (iter == m_attributes.end ())
+ m_key_insertion_order.push_back (name);
m_attributes[name] = std::move (value);
}
+// struct raw : public node
+
+void
+raw::write_as_xml (pretty_printer *pp,
+ int /*depth*/, bool /*indent*/) const
+{
+ pp_string (pp, m_xml_src.c_str ());
+}
+
#if __GNUC__ >= 10
# pragma GCC diagnostic pop
#endif
+// class printer
+
+printer::printer (element &insertion_point)
+{
+ m_open_tags.push_back (&insertion_point);
+}
+
+void
+printer::push_tag (std::string name,
+ bool preserve_whitespace)
+{
+ push_element
+ (std::make_unique<element> (std::move (name),
+ preserve_whitespace));
+}
+
+void
+printer::push_tag_with_class (std::string name, std::string class_,
+ bool preserve_whitespace)
+{
+ auto new_element
+ = std::make_unique<element> (std::move (name),
+ preserve_whitespace);
+ new_element->set_attr ("class", class_);
+ push_element (std::move (new_element));
+}
+
+void
+printer::pop_tag ()
+{
+ m_open_tags.pop_back ();
+}
+
+void
+printer::set_attr (const char *name, std::string value)
+{
+ m_open_tags.back ()->set_attr (name, value);
+}
+
+void
+printer::add_text (std::string text)
+{
+ element *parent = m_open_tags.back ();
+ parent->add_text (std::move (text));
+}
+
+void
+printer::add_raw (std::string text)
+{
+ element *parent = m_open_tags.back ();
+ parent->add_child (std::make_unique<xml::raw> (std::move (text)));
+}
+
+void
+printer::push_element (std::unique_ptr<element> new_element)
+{
+ element *parent = m_open_tags.back ();
+ m_open_tags.push_back (new_element.get ());
+ parent->add_child (std::move (new_element));
+}
+
+void
+printer::append (std::unique_ptr<node> new_node)
+{
+ element *parent = m_open_tags.back ();
+ parent->add_child (std::move (new_node));
+}
+
+element *
+printer::get_insertion_point () const
+{
+ return m_open_tags.back ();
+}
+
} // namespace xml
class html_builder;
html_builder (diagnostic_context &context,
pretty_printer &pp,
- const line_maps *line_maps);
+ const line_maps *line_maps,
+ const html_generation_options &html_gen_opts);
void on_report_diagnostic (const diagnostic_info &diagnostic,
diagnostic_t orig_diag_kind,
std::unique_ptr<xml::element>
make_element_for_metadata (const diagnostic_metadata &metadata);
- std::unique_ptr<xml::element>
- make_element_for_source (const diagnostic_info &diagnostic);
-
- std::unique_ptr<xml::element>
- make_element_for_path (const diagnostic_path &path);
-
std::unique_ptr<xml::element>
make_element_for_patch (const diagnostic_info &diagnostic);
+ void add_focus_id (std::string focus_id)
+ {
+ m_ui_focus_ids.append_string (focus_id.c_str ());
+ }
+
private:
std::unique_ptr<xml::element>
make_element_for_diagnostic (const diagnostic_info &diagnostic,
diagnostic_context &m_context;
pretty_printer *m_printer;
const line_maps *m_line_maps;
+ html_generation_options m_html_gen_opts;
std::unique_ptr<xml::document> m_document;
+ xml::element *m_head_element;
xml::element *m_diagnostics_element;
std::unique_ptr<xml::element> m_cur_diagnostic_element;
+ int m_next_diag_id; // for handing out unique IDs
+ json::array m_ui_focus_ids;
};
static std::unique_ptr<xml::element>
-make_div (label_text class_)
+make_div (std::string class_)
{
auto div = std::make_unique<xml::element> ("div", false);
div->set_attr ("class", std::move (class_));
}
static std::unique_ptr<xml::element>
-make_span (label_text class_)
+make_span (std::string class_)
{
auto span = std::make_unique<xml::element> ("span", true);
span->set_attr ("class", std::move (class_));
/* class html_builder. */
+/* Style information for writing out HTML paths.
+ Colors taken from https://www.patternfly.org/v3/styles/color-palette/ */
+
+static const char * const HTML_STYLE
+ = (" <style>\n"
+ " .linenum { color: white;\n"
+ " background-color: #0088ce;\n"
+ " white-space: pre;\n"
+ " border-right: 1px solid black; }\n"
+ " .ruler { color: red;\n"
+ " white-space: pre; }\n"
+ " .source { color: blue;\n"
+ " white-space: pre; }\n"
+ " .annotation { color: green;\n"
+ " white-space: pre; }\n"
+ " .linenum-gap { text-align: center;\n"
+ " border-top: 1px solid black;\n"
+ " border-right: 1px solid black;\n"
+ " background-color: #ededed; }\n"
+ " .source-gap { border-bottom: 1px dashed black;\n"
+ " border-top: 1px dashed black;\n"
+ " background-color: #ededed; }\n"
+ " .no-locus-event { font-family: monospace;\n"
+ " color: green;\n"
+ " white-space: pre; }\n"
+ " .funcname { font-weight: bold; }\n"
+ " .events-hdr { color: white;\n"
+ " background-color: #030303; }\n"
+ " .event-range { border: 1px solid black;\n"
+ " padding: 0px; }\n"
+ " .event-range-with-margin { border-spacing: 0; }\n"
+ " .locus { font-family: monospace;\n"
+ " border-spacing: 0px; }\n"
+ " .selected { color: white;\n"
+ " background-color: #0088ce; }\n"
+ " .stack-frame-with-margin { border-spacing: 0; }\n"
+ " .stack-frame { padding: 5px;\n"
+ " box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.5); }\n"
+ " .frame-funcname { text-align: right;\n"
+ " font-style: italic; } \n"
+ " </style>\n");
+
+/* A little JavaScript for ease of navigation.
+ Keys j/k move forward and backward cyclically through a list
+ of focus ids (written out in another <script> tag as the HTML
+ is flushed). */
+
+const char * const HTML_SCRIPT
+ = (" var current_focus_idx = 0;\n"
+ "\n"
+ " function get_focus_span (focus_idx)\n"
+ " {\n"
+ " const element_id = focus_ids[focus_idx];\n"
+ " return document.getElementById(element_id);\n"
+ " }\n"
+ " function unhighlight_current_focus_idx ()\n"
+ " {\n"
+ " get_focus_span (current_focus_idx).classList.remove ('selected');\n"
+ " }\n"
+ " function highlight_current_focus_idx ()\n"
+ " {\n"
+ " const el = get_focus_span (current_focus_idx);\n"
+ " el.classList.add ('selected');\n"
+ " // Center the element on the screen\n"
+ " const top_y = el.getBoundingClientRect ().top + window.pageYOffset;\n"
+ " const middle = top_y - (window.innerHeight / 2);\n"
+ " window.scrollTo (0, middle);\n"
+ " }\n"
+ " function select_prev_focus_idx ()\n"
+ " {\n"
+ " unhighlight_current_focus_idx ();\n"
+ " if (current_focus_idx > 0)\n"
+ " current_focus_idx -= 1;\n"
+ " else\n"
+ " current_focus_idx = focus_ids.length - 1;\n"
+ " highlight_current_focus_idx ();\n"
+ " }\n"
+ " function select_next_focus_idx ()\n"
+ " {\n"
+ " unhighlight_current_focus_idx ();\n"
+ " if (current_focus_idx < focus_ids.length - 1)\n"
+ " current_focus_idx += 1;\n"
+ " else\n"
+ " current_focus_idx = 0;\n"
+ " highlight_current_focus_idx ();\n"
+ " }\n"
+ " document.addEventListener('keydown', function (ev) {\n"
+ " if (ev.key == 'j')\n"
+ " select_next_focus_idx ();\n"
+ " else if (ev.key == 'k')\n"
+ " select_prev_focus_idx ();\n"
+ " });\n"
+ " highlight_current_focus_idx ();\n");
+
/* html_builder's ctor. */
html_builder::html_builder (diagnostic_context &context,
- pretty_printer &pp,
- const line_maps *line_maps)
+ pretty_printer &pp,
+ const line_maps *line_maps,
+ const html_generation_options &html_gen_opts)
: m_context (context),
m_printer (&pp),
- m_line_maps (line_maps)
+ m_line_maps (line_maps),
+ m_html_gen_opts (html_gen_opts),
+ m_head_element (nullptr),
+ m_diagnostics_element (nullptr),
+ m_next_diag_id (0)
{
gcc_assert (m_line_maps);
m_document = std::make_unique<xml::document> ();
{
auto html_element = std::make_unique<xml::element> ("html", false);
- html_element->set_attr
- ("xmlns",
- label_text::borrow ("http://www.w3.org/1999/xhtml"));
+ html_element->set_attr ("xmlns",
+ "http://www.w3.org/1999/xhtml");
+ xml::printer xp (*html_element.get ());
+ m_document->add_child (std::move (html_element));
+
{
+ xml::auto_print_element head (xp, "head");
+ m_head_element = xp.get_insertion_point ();
{
- auto head_element = std::make_unique<xml::element> ("head", false);
+ xml::auto_print_element title (xp, "title", true);
+ xp.add_text ("Title goes here");
+ }
+ if (m_html_gen_opts.m_css)
+ xp.add_raw (HTML_STYLE);
+ if (m_html_gen_opts.m_javascript)
{
- auto title_element = std::make_unique<xml::element> ("title", true);
- label_text title (label_text::borrow ("Title goes here")); // TODO
- title_element->add_text (std::move (title));
- head_element->add_child (std::move (title_element));
+ xp.push_tag ("script");
+ /* Escaping rules are different for HTML <script> elements,
+ so add the script "raw" for now. */
+ xp.add_raw (HTML_SCRIPT);
+ xp.pop_tag (); // script
}
- html_element->add_child (std::move (head_element));
+ }
- auto body_element = std::make_unique<xml::element> ("body", false);
- {
- auto diagnostics_element
- = make_div (label_text::borrow ("gcc-diagnostic-list"));
- m_diagnostics_element = diagnostics_element.get ();
- body_element->add_child (std::move (diagnostics_element));
- }
- html_element->add_child (std::move (body_element));
+ {
+ xml::auto_print_element body (xp, "body");
+ {
+ auto diagnostics_element = make_div ("gcc-diagnostic-list");
+ m_diagnostics_element = diagnostics_element.get ();
+ xp.append (std::move (diagnostics_element));
}
}
- m_document->add_child (std::move (html_element));
}
}
}
}
+/* Custom subclass of html_label_writer.
+ Wrap labels within a <span> element, supplying them with event IDs.
+ Add the IDs to the list of focus IDs. */
+
+class html_path_label_writer : public html_label_writer
+{
+public:
+ html_path_label_writer (xml::printer &xp,
+ html_builder &builder,
+ const std::string &event_id_prefix)
+ : m_xp (xp),
+ m_html_builder (builder),
+ m_event_id_prefix (event_id_prefix),
+ m_next_event_idx (0)
+ {
+ }
+
+ void begin_label () final override
+ {
+ m_xp.push_tag_with_class ("span", "event", true);
+ pretty_printer pp;
+ pp_printf (&pp, "%s%i",
+ m_event_id_prefix.c_str (), m_next_event_idx++);
+ m_xp.set_attr ("id", pp_formatted_text (&pp));
+ m_html_builder.add_focus_id (pp_formatted_text (&pp));
+ }
+
+ void end_label () final override
+ {
+ m_xp.pop_tag (); // span
+ }
+
+private:
+ xml::printer &m_xp;
+ html_builder &m_html_builder;
+ const std::string &m_event_id_prefix;
+ int m_next_event_idx;
+};
+
std::unique_ptr<xml::element>
html_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic,
diagnostic_t orig_diag_kind)
pp_token_text *sub = as_a <pp_token_text *> (iter);
/* The value might be in the obstack, so we may need to
copy it. */
- insertion_element ().add_text
- (label_text::take (xstrdup (sub->m_value.get ())));
+ insertion_element ().add_text (sub->m_value.get ());
}
break;
case pp_token::kind::begin_quote:
{
- insertion_element ().add_text (label_text::borrow (open_quote));
- push_element (make_span (label_text::borrow ("gcc-quoted-text")));
+ insertion_element ().add_text (open_quote);
+ push_element (make_span ("gcc-quoted-text"));
}
break;
case pp_token::kind::end_quote:
{
pop_element ();
- insertion_element ().add_text (label_text::borrow (close_quote));
+ insertion_element ().add_text (close_quote);
}
break;
{
pp_token_begin_url *sub = as_a <pp_token_begin_url *> (iter);
auto anchor = std::make_unique<xml::element> ("a", true);
- anchor->set_attr ("href", std::move (sub->m_value));
+ anchor->set_attr ("href", sub->m_value.get ());
push_element (std::move (anchor));
}
break;
std::vector<xml::element *> m_open_elements;
};
- auto diag_element = make_div (label_text::borrow ("gcc-diagnostic"));
+ auto diag_element = make_div ("gcc-diagnostic");
+
+ const int diag_idx = m_next_diag_id++;
+ std::string diag_id;
+ {
+ pretty_printer pp;
+ pp_printf (&pp, "gcc-diag-%i", diag_idx);
+ diag_id = pp_formatted_text (&pp);
+ }
+ diag_element->set_attr ("id", diag_id);
// TODO: might be nice to emulate the text output format, but colorize it
- auto message_span = make_span (label_text::borrow ("gcc-message"));
+ auto message_span = make_span ("gcc-message");
+ std::string message_span_id (diag_id + "-message");
+ message_span->set_attr ("id", message_span_id);
+ add_focus_id (message_span_id);
+
html_token_printer tok_printer (*this, *message_span.get ());
m_printer->set_token_printer (&tok_printer);
pp_output_formatted_text (m_printer, m_context.get_urlifier ());
if (diagnostic.metadata)
{
- diag_element->add_text (label_text::borrow (" "));
+ diag_element->add_text (" ");
diag_element->add_child
(make_element_for_metadata (*diagnostic.metadata));
}
label_text option_url = label_text::take
(m_context.make_option_url (diagnostic.option_id));
- diag_element->add_text (label_text::borrow (" "));
- auto option_span = make_span (label_text::borrow ("gcc-option"));
- option_span->add_text (label_text::borrow ("["));
+ diag_element->add_text (" ");
+ auto option_span = make_span ("gcc-option");
+ option_span->add_text ("[");
{
if (option_url.get ())
{
auto anchor = std::make_unique<xml::element> ("a", true);
- anchor->set_attr ("href", std::move (option_url));
- anchor->add_text (std::move (option_text));
+ anchor->set_attr ("href", option_url.get ());
+ anchor->add_text (option_text.get ());
option_span->add_child (std::move (anchor));
}
else
- option_span->add_text (std::move (option_text));
- option_span->add_text (label_text::borrow ("]"));
+ option_span->add_text (option_text.get ());
+ option_span->add_text ("]");
}
diag_element->add_child (std::move (option_span));
}
/* Source (and fix-it hints). */
- if (auto source_element = make_element_for_source (diagnostic))
- diag_element->add_child (std::move (source_element));
+ {
+ xml::printer xp (*diag_element);
+ m_context.m_last_location = UNKNOWN_LOCATION;
+ m_context.maybe_show_locus_as_html (*diagnostic.richloc,
+ m_context.m_source_printing,
+ diagnostic.kind,
+ xp,
+ nullptr,
+ nullptr);
+ }
/* Execution path. */
if (auto path = diagnostic.richloc->get_path ())
- if (auto path_element = make_element_for_path (*path))
- diag_element->add_child (std::move (path_element));
+ {
+ xml::printer xp (*diag_element);
+ std::string event_id_prefix (diag_id + "-event-");
+ html_path_label_writer event_label_writer (xp, *this,
+ event_id_prefix);
+ diagnostic_source_print_policy dspp (m_context);
+ print_path_as_html (xp, *path, m_context, &event_label_writer,
+ dspp);
+ }
if (auto patch_element = make_element_for_patch (diagnostic))
diag_element->add_child (std::move (patch_element));
return diag_element;
}
-std::unique_ptr<xml::element>
-html_builder::make_element_for_source (const diagnostic_info &diagnostic)
-{
- // TODO: ideally we'd like to capture elements within the following:
- m_context.m_last_location = UNKNOWN_LOCATION;
- pp_clear_output_area (m_printer);
- diagnostic_show_locus (&m_context,
- m_context.m_source_printing,
- diagnostic.richloc, diagnostic.kind,
- m_printer);
- auto text = label_text::take (xstrdup (pp_formatted_text (m_printer)));
- pp_clear_output_area (m_printer);
-
- if (strlen (text.get ()) == 0)
- return nullptr;
-
- auto pre = std::make_unique<xml::element> ("pre", true);
- pre->set_attr ("class", label_text::borrow ("gcc-annotated-source"));
- pre->add_text (std::move (text));
- return pre;
-}
-
-std::unique_ptr<xml::element>
-html_builder::make_element_for_path (const diagnostic_path &path)
-{
- m_context.m_last_location = UNKNOWN_LOCATION;
- diagnostic_text_output_format text_format (m_context);
- pp_show_color (text_format.get_printer ()) = false;
- pp_buffer (text_format.get_printer ())->m_flush_p = false;
- // TODO: ideally we'd like to capture elements within the following:
- text_format.print_path (path);
- auto text = label_text::take
- (xstrdup (pp_formatted_text (text_format.get_printer ())));
-
- if (strlen (text.get ()) == 0)
- return nullptr;
-
- auto pre = std::make_unique<xml::element> ("pre", true);
- pre->set_attr ("class", label_text::borrow ("gcc-execution-path"));
- pre->add_text (std::move (text));
- return pre;
-}
-
std::unique_ptr<xml::element>
html_builder::make_element_for_patch (const diagnostic_info &diagnostic)
{
edit_context ec (m_context.get_file_cache ());
ec.add_fixits (diagnostic.richloc);
if (char *diff = ec.generate_diff (true))
- if (strlen (diff) > 0)
- {
- auto element = std::make_unique<xml::element> ("pre", true);
- element->set_attr ("class", label_text::borrow ("gcc-generated-patch"));
- element->add_text (label_text::take (diff));
- return element;
- }
+ {
+ if (strlen (diff) > 0)
+ {
+ auto element = std::make_unique<xml::element> ("pre", true);
+ element->set_attr ("class", "gcc-generated-patch");
+ element->add_text (diff);
+ free (diff);
+ return element;
+ }
+ else
+ free (diff);
+ }
return nullptr;
}
html_builder::make_metadata_element (label_text label,
label_text url)
{
- auto item = make_span (label_text::borrow ("gcc-metadata-item"));
- item->add_text (label_text::borrow ("["));
+ auto item = make_span ("gcc-metadata-item");
+ xml::printer xp (*item.get ());
+ xp.add_text ("[");
{
- auto anchor = std::make_unique<xml::element> ("a", true);
- anchor->set_attr ("href", std::move (url));
- anchor->add_child (std::make_unique<xml::text> (std::move (label)));
- item->add_child (std::move (anchor));
+ xp.push_tag ("a", true);
+ xp.set_attr ("href", url.get ());
+ xp.add_text (label.get ());
+ xp.pop_tag ();
}
- item->add_text (label_text::borrow ("]"));
+ xp.add_text ("]");
return item;
}
std::unique_ptr<xml::element>
html_builder::make_element_for_metadata (const diagnostic_metadata &metadata)
{
- auto span_metadata = make_span (label_text::borrow ("gcc-metadata"));
+ auto span_metadata = make_span ("gcc-metadata");
int cwe = metadata.get_cwe ();
if (cwe)
/* We must be within the emission of a top-level diagnostic. */
gcc_assert (m_cur_diagnostic_element);
- // TODO
+ // TODO: currently a no-op
}
/* Implementation of "end_group_cb" for HTML output. */
void
html_builder::flush_to_file (FILE *outf)
{
+ if (m_html_gen_opts.m_javascript)
+ {
+ gcc_assert (m_head_element);
+ xml::printer xp (*m_head_element);
+ /* Add an initialization of the global js variable "focus_ids"
+ using the array of IDs we saved as we went. */
+ xp.push_tag ("script");
+ pretty_printer pp;
+ pp_string (&pp, "focus_ids = ");
+ m_ui_focus_ids.print (&pp, true);
+ pp_string (&pp, ";\n");
+ xp.add_raw (pp_formatted_text (&pp));
+ xp.pop_tag (); // script
+ }
auto top = m_document.get ();
top->dump (outf);
fprintf (outf, "\n");
protected:
html_output_format (diagnostic_context &context,
- const line_maps *line_maps)
+ const line_maps *line_maps,
+ const html_generation_options &html_gen_opts)
: diagnostic_output_format (context),
- m_builder (context, *get_printer (), line_maps),
+ m_builder (context, *get_printer (), line_maps, html_gen_opts),
m_buffer (nullptr)
{}
public:
html_file_output_format (diagnostic_context &context,
const line_maps *line_maps,
+ const html_generation_options &html_gen_opts,
diagnostic_output_file output_file)
- : html_output_format (context, line_maps),
+ : html_output_format (context, line_maps, html_gen_opts),
m_output_file (std::move (output_file))
{
gcc_assert (m_output_file.get_open_file ());
std::unique_ptr<diagnostic_output_format>
make_html_sink (diagnostic_context &context,
const line_maps &line_maps,
+ const html_generation_options &html_gen_opts,
diagnostic_output_file output_file)
{
auto sink
= std::make_unique<html_file_output_format> (context,
&line_maps,
+ html_gen_opts,
std::move (output_file));
sink->update_printer ();
return sink;
public:
test_html_diagnostic_context ()
{
+ html_generation_options html_gen_opts;
+ html_gen_opts.m_css = false;
+ html_gen_opts.m_javascript = false;
auto sink = std::make_unique<html_buffered_output_format> (*this,
- line_table);
+ line_table,
+ html_gen_opts);
sink->update_printer ();
m_format = sink.get (); // borrowed
{
public:
html_buffered_output_format (diagnostic_context &context,
- const line_maps *line_maps)
- : html_output_format (context, line_maps)
+ const line_maps *line_maps,
+ const html_generation_options &html_gen_opts)
+ : html_output_format (context, line_maps, html_gen_opts)
{
}
bool machine_readable_stderr_p () const final override
" </head>\n"
" <body>\n"
" <div class=\"gcc-diagnostic-list\">\n"
- " <div class=\"gcc-diagnostic\">\n"
- " <span class=\"gcc-message\">this is a test: `<span class=\"gcc-quoted-text\">foo</span>'</span>\n"
+ " <div class=\"gcc-diagnostic\" id=\"gcc-diag-0\">\n"
+ " <span class=\"gcc-message\" id=\"gcc-diag-0-message\">this is a test: `<span class=\"gcc-quoted-text\">foo</span>'</span>\n"
" </div>\n"
" </div>\n"
" </body>\n"
- "</html>"));
+ "</html>\n"));
}
static void
element->write_as_xml (&pp, 0, true);
ASSERT_STREQ
(pp_formatted_text (&pp),
- "\n"
"<span class=\"gcc-metadata\">"
"<span class=\"gcc-metadata-item\">"
"["
"</a>"
"]"
"</span>"
- "</span>");
+ "</span>\n");
}
{
element->write_as_xml (&pp, 0, true);
ASSERT_STREQ
(pp_formatted_text (&pp),
- "\n"
"<span class=\"gcc-metadata\">"
"<span class=\"gcc-metadata-item\">"
"["
"</a>"
"]"
"</span>"
- "</span>");
+ "</span>\n");
}
}
+static void
+test_printer ()
+{
+ xml::element top ("top", false);
+ xml::printer xp (top);
+ xp.push_tag ("foo");
+ xp.add_text ("hello");
+ xp.push_tag ("bar");
+ xp.set_attr ("size", "3");
+ xp.set_attr ("color", "red");
+ xp.add_text ("world");
+ xp.push_tag ("baz");
+ xp.pop_tag ();
+ xp.pop_tag ();
+ xp.pop_tag ();
+
+ pretty_printer pp;
+ top.write_as_xml (&pp, 0, true);
+ ASSERT_STREQ
+ (pp_formatted_text (&pp),
+ "<top>\n"
+ " <foo>\n"
+ " hello\n"
+ " <bar size=\"3\" color=\"red\">\n"
+ " world\n"
+ " <baz/>\n"
+ " </bar>\n"
+ " </foo>\n"
+ "</top>\n");
+}
+
+// Verify that element attributes preserve insertion order.
+
+static void
+test_attribute_ordering ()
+{
+ xml::element top ("top", false);
+ xml::printer xp (top);
+ xp.push_tag ("chronological");
+ xp.set_attr ("maldon", "991");
+ xp.set_attr ("hastings", "1066");
+ xp.set_attr ("edgehill", "1642");
+ xp.set_attr ("naseby", "1645");
+ xp.pop_tag ();
+ xp.push_tag ("alphabetical");
+ xp.set_attr ("edgehill", "1642");
+ xp.set_attr ("hastings", "1066");
+ xp.set_attr ("maldon", "991");
+ xp.set_attr ("naseby", "1645");
+ xp.pop_tag ();
+
+ pretty_printer pp;
+ top.write_as_xml (&pp, 0, true);
+ ASSERT_STREQ
+ (pp_formatted_text (&pp),
+ "<top>\n"
+ " <chronological maldon=\"991\" hastings=\"1066\" edgehill=\"1642\" naseby=\"1645\"/>\n"
+ " <alphabetical edgehill=\"1642\" hastings=\"1066\" maldon=\"991\" naseby=\"1645\"/>\n"
+ "</top>\n");
+}
+
/* Run all of the selftests within this file. */
void
auto_fix_quotes fix_quotes;
test_simple_log ();
test_metadata ();
+ test_printer ();
+ test_attribute_ordering ();
}
} // namespace selftest
#include "diagnostic-format.h"
#include "diagnostic-output-file.h"
+struct html_generation_options
+{
+ html_generation_options ();
+
+ bool m_css;
+ bool m_javascript;
+};
+
extern diagnostic_output_file
diagnostic_output_format_open_html_file (diagnostic_context &context,
line_maps *line_maps,
extern std::unique_ptr<diagnostic_output_format>
make_html_sink (diagnostic_context &context,
const line_maps &line_maps,
+ const html_generation_options &html_gen_opts,
diagnostic_output_file output_file);
+extern void
+print_path_as_html (xml::printer &xp,
+ const diagnostic_path &path,
+ diagnostic_context &dc,
+ html_label_writer *event_label_writer,
+ const diagnostic_source_print_policy &dspp);
+
#endif /* ! GCC_DIAGNOSTIC_FORMAT_HTML_H */
#include "config.h"
#define INCLUDE_ALGORITHM
+#define INCLUDE_MAP
#define INCLUDE_STRING
#define INCLUDE_VECTOR
#include "system.h"
#include "selftest-diagnostic-path.h"
#include "text-art/theme.h"
#include "diagnostic-format-text.h"
+#include "diagnostic-format-html.h"
+#include "xml.h"
+#include "xml-printer.h"
/* Disable warnings about missing quoting in GCC diagnostics for the print
calls below. */
{
}
+ path_print_policy (const diagnostic_context &dc)
+ : m_source_policy (dc)
+ {
+ }
+
text_art::theme *
get_diagram_theme () const
{
- return m_source_policy.get_diagram_theme ();
+ return m_source_policy.get_diagram_theme ();
}
const diagnostic_source_print_policy &
int m_max_depth;
};
+/* A stack frame for use in HTML output, holding child stack frames,
+ and event ranges. */
+
+struct stack_frame
+{
+ stack_frame (std::unique_ptr<stack_frame> parent,
+ logical_location logical_loc,
+ int stack_depth)
+ : m_parent (std::move (parent)),
+ m_logical_loc (logical_loc),
+ m_stack_depth (stack_depth)
+ {}
+
+ std::unique_ptr<stack_frame> m_parent;
+ logical_location m_logical_loc;
+ const int m_stack_depth;
+};
+
+/* Begin emitting content relating to a new stack frame within PARENT.
+ Allocated a new stack_frame and return it. */
+
+static std::unique_ptr<stack_frame>
+begin_html_stack_frame (xml::printer &xp,
+ std::unique_ptr<stack_frame> parent,
+ logical_location logical_loc,
+ int stack_depth,
+ const logical_location_manager *logical_loc_mgr)
+{
+ if (logical_loc)
+ {
+ gcc_assert (logical_loc_mgr);
+ xp.push_tag_with_class ("table", "stack-frame-with-margin", false);
+ xp.push_tag ("tr", false);
+ {
+ xp.push_tag_with_class ("td", "interprocmargin", false);
+ xp.set_attr ("style", "padding-left: 100px");
+ xp.pop_tag ();
+ }
+ xp.push_tag_with_class ("td", "stack-frame", false);
+ label_text funcname
+ = logical_loc_mgr->get_name_for_path_output (logical_loc);
+ if (funcname.get ())
+ {
+ xp.push_tag_with_class ("div", "frame-funcname", false);
+ xp.push_tag ("span", true);
+ xp.add_text (funcname.get ());
+ xp.pop_tag (); // span
+ xp.pop_tag (); // div
+ }
+ }
+ return std::make_unique<stack_frame> (std::move (parent),
+ logical_loc,
+ stack_depth);
+}
+
+/* Finish emitting content for FRAME and delete it.
+ Return parent. */
+
+static std::unique_ptr<stack_frame>
+end_html_stack_frame (xml::printer &xp,
+ std::unique_ptr<stack_frame> frame)
+{
+ auto parent = std::move (frame->m_parent);
+ if (frame->m_logical_loc)
+ {
+ xp.pop_tag (); // td
+ xp.pop_tag (); // tr
+ xp.pop_tag (); // table
+ }
+ return parent;
+}
+
+/* Append an HTML <div> element to XP containing an SVG arrow representing
+ a change in stack depth from OLD_DEPTH to NEW_DEPTH. */
+
+static void
+emit_svg_arrow (xml::printer &xp, int old_depth, int new_depth)
+{
+ const int pixels_per_depth = 100;
+ const int min_depth = MIN (old_depth, new_depth);
+ const int base_x = 20;
+ const int excess = 30;
+ const int last_x
+ = base_x + (old_depth - min_depth) * pixels_per_depth;
+ const int this_x
+ = base_x + (new_depth - min_depth) * pixels_per_depth;
+ pretty_printer tmp_pp;
+ pretty_printer *pp = &tmp_pp;
+ pp_printf (pp, "<div class=\"%s\">\n",
+ old_depth < new_depth
+ ? "between-ranges-call" : "between-ranges-return");
+ pp_printf (pp, " <svg height=\"30\" width=\"%i\">\n",
+ MAX (last_x, this_x) + excess);
+ pp_string
+ (pp,
+ " <defs>\n"
+ " <marker id=\"arrowhead\" markerWidth=\"10\" markerHeight=\"7\"\n"
+ " refX=\"0\" refY=\"3.5\" orient=\"auto\" stroke=\"#0088ce\" fill=\"#0088ce\">\n"
+ " <polygon points=\"0 0, 10 3.5, 0 7\"/>\n"
+ " </marker>\n"
+ " </defs>\n");
+ pp_printf (pp,
+ " <polyline points=\"%i,0 %i,10 %i,10 %i,20\"\n",
+ last_x, last_x, this_x, this_x);
+ pp_string
+ (pp,
+ " style=\"fill:none;stroke: #0088ce\"\n"
+ " marker-end=\"url(#arrowhead)\"/>\n"
+ " </svg>\n"
+ "</div>\n\n");
+ xp.add_raw (pp_formatted_text (pp));
+}
+
/* A range of consecutive events within a diagnostic_path, all within the
same thread, and with the same fndecl and stack_depth, and which are suitable
to print with a single call to diagnostic_show_locus. */
/* Print the events in this range to PP, typically as a single
call to diagnostic_show_locus. */
- void print (pretty_printer &pp,
- diagnostic_text_output_format &text_output,
- diagnostic_source_effect_info *effect_info)
+ void print_as_text (pretty_printer &pp,
+ diagnostic_text_output_format &text_output,
+ diagnostic_source_effect_info *effect_info)
{
location_t initial_loc = m_initial_event.get_location ();
if (exploc.file != LOCATION_FILE (dc.m_last_location))
{
diagnostic_location_print_policy loc_policy (text_output);
- diagnostic_start_span (&dc) (loc_policy, &pp, exploc);
+ loc_policy.print_text_span_start (dc, pp, exploc);
}
}
}
}
+ /* Print the events in this range to XP, typically as a single
+ call to diagnostic_show_locus_as_html. */
+
+ void print_as_html (xml::printer &xp,
+ diagnostic_context &dc,
+ diagnostic_source_effect_info *effect_info,
+ html_label_writer *event_label_writer)
+ {
+ location_t initial_loc = m_initial_event.get_location ();
+
+ /* Emit a span indicating the filename (and line/column) if the
+ line has changed relative to the last call to
+ diagnostic_show_locus. */
+ if (dc.m_source_printing.enabled)
+ {
+ expanded_location exploc
+ = linemap_client_expand_location_to_spelling_point
+ (line_table, initial_loc, LOCATION_ASPECT_CARET);
+ if (exploc.file != LOCATION_FILE (dc.m_last_location))
+ {
+ diagnostic_location_print_policy loc_policy (dc);
+ loc_policy.print_html_span_start (dc, xp, exploc);
+ }
+ }
+
+ /* If we have an UNKNOWN_LOCATION (or BUILTINS_LOCATION) as the
+ primary location for an event, diagnostic_show_locus_as_html won't print
+ anything.
+
+ In particular the label for the event won't get printed.
+ Fail more gracefully in this case by showing the event
+ index and text, at no particular location. */
+ if (get_pure_location (initial_loc) <= BUILTINS_LOCATION)
+ {
+ for (unsigned i = m_start_idx; i <= m_end_idx; i++)
+ {
+ const diagnostic_event &iter_event = m_path.get_event (i);
+ diagnostic_event_id_t event_id (i);
+ pretty_printer pp;
+ pp_printf (&pp, " %@: ", &event_id);
+ iter_event.print_desc (pp);
+ if (event_label_writer)
+ event_label_writer->begin_label ();
+ xp.add_text (pp_formatted_text (&pp));
+ if (event_label_writer)
+ event_label_writer->end_label ();
+ }
+ return;
+ }
+
+ /* Call diagnostic_show_locus_as_html to show the source,
+ showing events using labels. */
+ diagnostic_show_locus_as_html (&dc, dc.m_source_printing,
+ &m_richloc, DK_DIAGNOSTIC_PATH, xp,
+ effect_info, event_label_writer);
+
+ // TODO: show macro expansions
+ }
+
const diagnostic_path &m_path;
const diagnostic_event &m_initial_event;
logical_location m_logical_loc;
}
void
- print_swimlane_for_event_range (diagnostic_text_output_format &text_output,
- pretty_printer *pp,
- const logical_location_manager &logical_loc_mgr,
- event_range *range,
- diagnostic_source_effect_info *effect_info)
+ print_swimlane_for_event_range_as_text (diagnostic_text_output_format &text_output,
+ pretty_printer *pp,
+ const logical_location_manager &logical_loc_mgr,
+ event_range *range,
+ diagnostic_source_effect_info *effect_info)
{
gcc_assert (pp);
const char *const line_color = "path";
}
pp_set_prefix (pp, prefix);
pp_prefixing_rule (pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
- range->print (*pp, text_output, effect_info);
+ range->print_as_text (*pp, text_output, effect_info);
pp_set_prefix (pp, saved_prefix);
write_indent (pp, m_cur_indent + per_frame_indent);
pp_newline (pp);
}
else
- range->print (*pp, text_output, effect_info);
+ range->print_as_text (*pp, text_output, effect_info);
if (const event_range *next_range = get_any_next_range ())
{
m_num_printed++;
}
+ void
+ print_swimlane_for_event_range_as_html (diagnostic_context &dc,
+ xml::printer &xp,
+ html_label_writer *event_label_writer,
+ event_range *range,
+ diagnostic_source_effect_info *effect_info)
+ {
+ range->print_as_html (xp, dc, effect_info, event_label_writer);
+ m_num_printed++;
+ }
+
int get_cur_indent () const { return m_cur_indent; }
private:
of this range. */
diagnostic_source_effect_info effect_info;
effect_info.m_leading_in_edge_column = last_out_edge_column;
- tep.print_swimlane_for_event_range (text_output, pp,
- ps.get_logical_location_manager (),
- range, &effect_info);
+ tep.print_swimlane_for_event_range_as_text
+ (text_output, pp,
+ ps.get_logical_location_manager (),
+ range, &effect_info);
+ last_out_edge_column = effect_info.m_trailing_out_edge_column;
+ }
+}
+
+/* Print PS as HTML to XP, using DC and, if non-null EVENT_LABEL_WRITER. */
+
+static void
+print_path_summary_as_html (const path_summary &ps,
+ diagnostic_context &dc,
+ xml::printer &xp,
+ html_label_writer *event_label_writer,
+ bool show_depths)
+{
+ std::vector<thread_event_printer> thread_event_printers;
+ for (auto t : ps.m_per_thread_summary)
+ thread_event_printers.push_back (thread_event_printer (*t, show_depths));
+
+ const logical_location_manager *logical_loc_mgr
+ = dc.get_logical_location_manager ();
+
+ xp.push_tag_with_class ("div", "event-ranges", false);
+
+ /* Group the ranges into stack frames. */
+ std::unique_ptr<stack_frame> curr_frame;
+ unsigned i;
+ event_range *range;
+ int last_out_edge_column = -1;
+ FOR_EACH_VEC_ELT (ps.m_ranges, i, range)
+ {
+ const int swimlane_idx
+ = range->m_per_thread_summary.get_swimlane_index ();
+
+ const logical_location this_logical_loc = range->m_logical_loc;
+ const int this_depth = range->m_stack_depth;
+ if (curr_frame)
+ {
+ int old_stack_depth = curr_frame->m_stack_depth;
+ if (this_depth > curr_frame->m_stack_depth)
+ {
+ emit_svg_arrow (xp, old_stack_depth, this_depth);
+ curr_frame
+ = begin_html_stack_frame (xp,
+ std::move (curr_frame),
+ range->m_logical_loc,
+ range->m_stack_depth,
+ logical_loc_mgr);
+ }
+ else
+ {
+ while (this_depth < curr_frame->m_stack_depth
+ || this_logical_loc != curr_frame->m_logical_loc)
+ {
+ curr_frame = end_html_stack_frame (xp, std::move (curr_frame));
+ if (curr_frame == NULL)
+ {
+ curr_frame
+ = begin_html_stack_frame (xp,
+ nullptr,
+ range->m_logical_loc,
+ range->m_stack_depth,
+ logical_loc_mgr);
+ break;
+ }
+ }
+ emit_svg_arrow (xp, old_stack_depth, this_depth);
+ }
+ }
+ else
+ {
+ curr_frame = begin_html_stack_frame (xp,
+ NULL,
+ range->m_logical_loc,
+ range->m_stack_depth,
+ logical_loc_mgr);
+ }
+
+ xp.push_tag_with_class ("table", "event-range-with-margin", false);
+ xp.push_tag ("tr", false);
+ xp.push_tag_with_class ("td", "event-range", false);
+ xp.push_tag_with_class ("div", "events-hdr", true);
+ if (range->m_logical_loc)
+ {
+ gcc_assert (logical_loc_mgr);
+ label_text funcname
+ = logical_loc_mgr->get_name_for_path_output (range->m_logical_loc);
+ if (funcname.get ())
+ {
+ xp.push_tag_with_class ("span", "funcname", true);
+ xp.add_text (funcname.get ());
+ xp.pop_tag (); //span
+ xp.add_text (": ");
+ }
+ }
+ {
+ xp.push_tag_with_class ("span", "event-ids", true);
+ pretty_printer pp;
+ if (range->m_start_idx == range->m_end_idx)
+ pp_printf (&pp, "event %i",
+ range->m_start_idx + 1);
+ else
+ pp_printf (&pp, "events %i-%i",
+ range->m_start_idx + 1, range->m_end_idx + 1);
+ xp.add_text (pp_formatted_text (&pp));
+ xp.pop_tag (); // span
+ }
+ if (show_depths)
+ {
+ xp.add_text (" ");
+ xp.push_tag_with_class ("span", "depth", true);
+ pretty_printer pp;
+ pp_printf (&pp, "(depth %i)", range->m_stack_depth);
+ xp.add_text (pp_formatted_text (&pp));
+ xp.pop_tag (); //span
+ }
+ xp.pop_tag (); // div
+
+ /* Print a run of events. */
+ thread_event_printer &tep = thread_event_printers[swimlane_idx];
+ /* Wire up any trailing out-edge from previous range to leading in-edge
+ of this range. */
+ diagnostic_source_effect_info effect_info;
+ effect_info.m_leading_in_edge_column = last_out_edge_column;
+ tep.print_swimlane_for_event_range_as_html (dc, xp, event_label_writer,
+ range, &effect_info);
last_out_edge_column = effect_info.m_trailing_out_edge_column;
+
+ xp.pop_tag (); // td
+ xp.pop_tag (); // tr
+ xp.pop_tag (); // table
}
+
+ /* Close outstanding frames. */
+ while (curr_frame)
+ curr_frame = end_html_stack_frame (xp, std::move (curr_frame));
+
+ xp.pop_tag (); // div
}
} /* end of anonymous namespace for path-printing code. */
}
}
+/* Print PATH as HTML to XP, using DC and DSPP for settings.
+ If non-null, use EVENT_LABEL_WRITER when writing events. */
+
+void
+print_path_as_html (xml::printer &xp,
+ const diagnostic_path &path,
+ diagnostic_context &dc,
+ html_label_writer *event_label_writer,
+ const diagnostic_source_print_policy &dspp)
+{
+ path_print_policy policy (dc);
+ const bool check_rich_locations = true;
+ const bool colorize = false;
+ const diagnostic_source_printing_options &source_printing_opts
+ = dspp.get_options ();
+ const bool show_event_links = source_printing_opts.show_event_links_p;
+ path_summary summary (policy,
+ *dc.get_reference_printer (),
+ path,
+ check_rich_locations,
+ colorize,
+ show_event_links);
+ print_path_summary_as_html (summary, dc, xp, event_label_writer,
+ dc.show_path_depths_p ());
+}
+
#if CHECKING_P
namespace selftest {
<http://www.gnu.org/licenses/>. */
#include "config.h"
+#define INCLUDE_MAP
+#define INCLUDE_STRING
#define INCLUDE_VECTOR
#include "system.h"
#include "coretypes.h"
#include "text-art/types.h"
#include "text-art/theme.h"
#include "diagnostic-label-effects.h"
+#include "xml.h"
+#include "xml-printer.h"
#ifdef HAVE_TERMIOS_H
# include <termios.h>
bool draw_caret_p;
};
-/* A class to inject colorization codes when printing the diagnostic locus.
+/* A class to inject colorization codes when printing the diagnostic locus
+ as text.
It has one kind of colorization for each of:
- normal text
public:
char_display_policy (int tabstop,
int (*width_cb) (cppchar_t c),
- void (*print_cb) (pretty_printer *pp,
- const cpp_decoded_char &cp))
+ void (*print_text_cb) (to_text &sink,
+ const cpp_decoded_char &cp),
+ void (*print_html_cb) (to_html &sink,
+ const cpp_decoded_char &cp))
: cpp_char_column_policy (tabstop, width_cb),
- m_print_cb (print_cb)
+ m_print_text_cb (print_text_cb),
+ m_print_html_cb (print_html_cb)
{
}
- void (*m_print_cb) (pretty_printer *pp,
- const cpp_decoded_char &cp);
+ void (*m_print_text_cb) (to_text &sink,
+ const cpp_decoded_char &cp);
+ void (*m_print_html_cb) (to_html &sink,
+ const cpp_decoded_char &cp);
};
+template <typename Sink> class layout_printer;
+
+} // anonymous namespace
+
+/* This code is written generically to write either:
+ - text, to a pretty_printer, potentially with colorization codes, or
+ - html, to an xml::printer, with nested HTML tags.
+
+ This is handled via a "Sink" template, which is either to_text
+ or to_html. */
+
+/* Writing text output. */
+
+struct to_text
+{
+ friend class layout_printer<to_text>;
+
+ to_text (pretty_printer &pp,
+ colorizer &colorizer)
+ : m_pp (pp),
+ m_colorizer (&colorizer)
+ {
+ m_saved_rule = pp_prefixing_rule (&m_pp);
+ pp_prefixing_rule (&m_pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
+ }
+ to_text (pretty_printer &pp,
+ colorizer *colorizer)
+ : m_pp (pp),
+ m_colorizer (colorizer)
+ {
+ m_saved_rule = pp_prefixing_rule (&m_pp);
+ pp_prefixing_rule (&m_pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
+ }
+ ~to_text ()
+ {
+
+ pp_prefixing_rule (&m_pp) = m_saved_rule;
+ }
+
+ static bool is_text () { return true; }
+ static bool is_html () { return false; }
+
+ void emit_text_prefix ()
+ {
+ pp_emit_prefix (&m_pp);
+ }
+
+ void push_html_tag (std::string, bool)
+ {
+ // no-op for text
+ }
+ void push_html_tag_with_class (std::string, std::string, bool)
+ {
+ // no-op for text
+ }
+ void pop_html_tag (std::string)
+ {
+ // no-op for text
+ }
+
+ void add_html_tag_with_class (std::string, std::string, bool)
+ {
+ // no-op for text
+ }
+
+ void add_space ()
+ {
+ pp_space (&m_pp);
+ }
+
+ void add_character (cppchar_t ch)
+ {
+ pp_unicode_character (&m_pp, ch);
+ }
+
+ void add_utf8_byte (char b)
+ {
+ pp_character (&m_pp, b);
+ }
+
+ void add_text (const char *text)
+ {
+ pp_string (&m_pp, text);
+ }
+
+ void print_decoded_char (const char_display_policy &char_policy,
+ cpp_decoded_char cp)
+ {
+ char_policy.m_print_text_cb (*this, cp);
+ }
+
+ void colorize_text_ensure_normal ()
+ {
+ gcc_assert (m_colorizer);
+ m_colorizer->set_normal_text ();
+ }
+
+ void colorize_text_for_range_idx (int range_idx)
+ {
+ gcc_assert (m_colorizer);
+ m_colorizer->set_range (range_idx);
+ }
+
+ void colorize_text_for_cfg_edge ()
+ {
+ gcc_assert (m_colorizer);
+ m_colorizer->set_cfg_edge ();
+ }
+
+ void colorize_text_for_fixit_insert ()
+ {
+ gcc_assert (m_colorizer);
+ m_colorizer->set_fixit_insert ();
+ }
+
+ void colorize_text_for_fixit_delete ()
+ {
+ gcc_assert (m_colorizer);
+ m_colorizer->set_fixit_delete ();
+ }
+
+ void
+ invoke_start_span_fn (const diagnostic_source_print_policy &source_policy,
+ const diagnostic_location_print_policy &loc_policy,
+ const expanded_location &exploc)
+ {
+ source_policy.get_text_start_span_fn () (loc_policy, *this, exploc);
+ }
+
+ // Text-specific functions
+ void add_newline ()
+ {
+ pp_newline (&m_pp);
+ }
+
+ pretty_printer &m_pp;
+private:
+ colorizer *m_colorizer;
+ diagnostic_prefixing_rule_t m_saved_rule;
+};
+
+/* Writing HTML output. */
+
+struct to_html
+{
+ friend class layout_printer<to_html>;
+
+ to_html (xml::printer &xp,
+ html_label_writer *html_label_writer)
+ : m_xp (xp),
+ m_html_label_writer (html_label_writer)
+ {}
+
+ static bool is_text () { return false; }
+ static bool is_html () { return true; }
+
+ void emit_text_prefix ()
+ {
+ // no-op for HTML
+ }
+
+ void push_html_tag (std::string name,
+ bool preserve_whitespace)
+ {
+ m_xp.push_tag (std::move (name), preserve_whitespace);
+ }
+
+ void push_html_tag_with_class (std::string name,
+ std::string class_,
+ bool preserve_whitespace)
+ {
+ m_xp.push_tag_with_class (std::move (name),
+ std::move (class_),
+ preserve_whitespace);
+ }
+
+ void pop_html_tag (std::string /*name*/)
+ {
+ m_xp.pop_tag ();
+ }
+
+ void add_html_tag_with_class (std::string name,
+ std::string class_,
+ bool preserve_whitespace)
+ {
+ auto element = std::make_unique<xml::element> (std::move (name),
+ preserve_whitespace);
+ element->set_attr ("class", std::move (class_));
+ m_xp.append (std::move (element));
+ }
+
+ void add_raw_html (const char *src)
+ {
+ m_xp.add_raw (src);
+ }
+
+ void add_space ()
+ {
+ m_xp.add_text (" ");
+ }
+
+ void add_character (cppchar_t ch)
+ {
+ pp_clear_output_area (&m_scratch_pp);
+ pp_unicode_character (&m_scratch_pp, ch);
+ m_xp.add_text (pp_formatted_text (&m_scratch_pp));
+ }
+
+ void add_utf8_byte (char b)
+ {
+ m_xp.add_text (std::string (1, b));
+ }
+
+ void add_text (const char *text)
+ {
+ m_xp.add_text (text);
+ }
+
+ void print_decoded_char (const char_display_policy &char_policy,
+ cpp_decoded_char cp)
+ {
+ char_policy.m_print_html_cb (*this, cp);
+ }
+
+ void colorize_text_ensure_normal ()
+ {
+ // no-op for HTML
+ }
+
+ void colorize_text_for_range_idx (int)
+ {
+ // no-op for HTML
+ }
+
+ void colorize_text_for_cfg_edge ()
+ {
+ // no-op for HTML
+ }
+
+ void colorize_text_for_fixit_insert ()
+ {
+ // no-op for HTML
+ }
+
+ void colorize_text_for_fixit_delete ()
+ {
+ // no-op for HTML
+ }
+
+ void
+ invoke_start_span_fn (const diagnostic_source_print_policy &source_policy,
+ const diagnostic_location_print_policy &loc_policy,
+ const expanded_location &exploc)
+ {
+ source_policy.get_html_start_span_fn () (loc_policy, *this, exploc);
+ }
+
+ xml::printer &m_xp;
+private:
+ html_label_writer *m_html_label_writer;
+ pretty_printer m_scratch_pp;
+};
+
+void
+diagnostic_location_print_policy::
+print_text_span_start (const diagnostic_context &dc,
+ pretty_printer &pp,
+ const expanded_location &exploc)
+{
+ to_text sink (pp, nullptr);
+ diagnostic_source_print_policy source_policy (dc);
+ source_policy.get_text_start_span_fn () (*this, sink, exploc);
+}
+
+void
+diagnostic_location_print_policy::
+print_html_span_start (const diagnostic_context &dc,
+ xml::printer &xp,
+ const expanded_location &exploc)
+{
+ to_html sink (xp, nullptr);
+ diagnostic_source_print_policy source_policy (dc);
+ source_policy.get_html_start_span_fn () (*this, sink, exploc);
+}
+
+pretty_printer *
+get_printer (to_text &sink)
+{
+ return &sink.m_pp;
+}
+
+template<>
+void
+default_diagnostic_start_span_fn<to_text> (const diagnostic_location_print_policy &loc_policy,
+ to_text &sink,
+ expanded_location exploc)
+{
+ const diagnostic_column_policy &column_policy
+ = loc_policy.get_column_policy ();
+ label_text text
+ = column_policy.get_location_text (exploc,
+ loc_policy.show_column_p (),
+ pp_show_color (&sink.m_pp));
+ pp_string (&sink.m_pp, text.get ());
+ pp_newline (&sink.m_pp);
+}
+
+template<>
+void
+default_diagnostic_start_span_fn<to_html> (const diagnostic_location_print_policy &loc_policy,
+ to_html &sink,
+ expanded_location exploc)
+{
+ const diagnostic_column_policy &column_policy
+ = loc_policy.get_column_policy ();
+ label_text text
+ = column_policy.get_location_text (exploc,
+ loc_policy.show_column_p (),
+ false);
+ sink.m_xp.push_tag_with_class ("span", "location", true);
+ sink.m_xp.add_text (text.get ());
+ sink.m_xp.pop_tag (); // span
+}
+
+namespace {
+
/* A class to control the overall layout when printing a diagnostic.
The layout is determined within the constructor.
class layout
{
public:
- friend class layout_printer;
+ friend class layout_printer<to_text>;
+ friend class layout_printer<to_html>;
layout (const diagnostic_source_print_policy &source_policy,
const rich_location &richloc,
bool m_escape_on_output;
};
-/* A bundle of state for printing a particular layout
- to a particular pretty_printer. */
+class line_label;
+enum class margin_kind
+{
+ normal,
+ insertion,
+ ruler
+};
+
+/* A bundle of state for printing a particular layout
+ to a particular Sink (either to_text or to_html). */
+template <typename Sink>
class layout_printer
{
public:
- layout_printer (pretty_printer &pp,
+ layout_printer (Sink &sink,
const layout &layout,
- const rich_location &richloc,
- diagnostic_t diagnostic_kind);
+ bool is_diagnostic_path);
void print (const diagnostic_source_print_policy &source_policy);
line_bounds print_source_line (linenum_type row, const char *line,
int line_bytes);
void print_leftmost_column ();
- void start_annotation_line (char margin_char = ' ');
+ void start_annotation_line (enum margin_kind);
+ void end_line ();
void print_annotation_line (linenum_type row, const line_bounds lbounds);
void print_any_labels (linenum_type row);
+ void begin_label (const line_label &label);
+ void end_label ();
void print_trailing_fixits (linenum_type row);
- void print_newline ();
void
move_to_column (int *column, int dest_column, bool add_left_margin);
void print_any_right_to_left_edge_lines ();
private:
- pretty_printer &m_pp;
+ Sink &m_sink;
const layout &m_layout;
- colorizer m_colorizer;
bool m_is_diagnostic_path;
/* Fields for handling links between labels (e.g. for showing CFG edges
/* Callback for char_display_policy::m_print_cb for printing source chars
when not escaping the source. */
+template <class Sink>
static void
-default_print_decoded_ch (pretty_printer *pp,
+default_print_decoded_ch (Sink &sink,
const cpp_decoded_char &decoded_ch)
{
for (const char *ptr = decoded_ch.m_start_byte;
{
if (*ptr == '\0' || *ptr == '\r')
{
- pp_space (pp);
+ sink.add_space ();
continue;
}
- pp_character (pp, *ptr);
+ sink.add_utf8_byte (*ptr);
}
}
/* Callback for char_display_policy::m_print_cb for printing source chars
when escaping with DIAGNOSTICS_ESCAPE_FORMAT_BYTES. */
+template <typename Sink>
static void
-escape_as_bytes_print (pretty_printer *pp,
+escape_as_bytes_print (Sink &sink,
const cpp_decoded_char &decoded_ch)
{
if (!decoded_ch.m_valid_ch)
{
char buf[16];
sprintf (buf, "<%02x>", (unsigned char)*iter);
- pp_string (pp, buf);
+ sink.add_text (buf);
}
return;
}
cppchar_t ch = decoded_ch.m_ch;
if (ch < 0x80 && ISPRINT (ch))
- pp_character (pp, ch);
+ sink.add_character (ch);
else
{
for (const char *iter = decoded_ch.m_start_byte;
{
char buf[16];
sprintf (buf, "<%02x>", (unsigned char)*iter);
- pp_string (pp, buf);
+ sink.add_text (buf);
}
}
}
/* Callback for char_display_policy::m_print_cb for printing source chars
when escaping with DIAGNOSTICS_ESCAPE_FORMAT_UNICODE. */
+template <typename Sink>
static void
-escape_as_unicode_print (pretty_printer *pp,
+escape_as_unicode_print (Sink &sink,
const cpp_decoded_char &decoded_ch)
{
if (!decoded_ch.m_valid_ch)
{
- escape_as_bytes_print (pp, decoded_ch);
+ escape_as_bytes_print<Sink> (sink, decoded_ch);
return;
}
cppchar_t ch = decoded_ch.m_ch;
if (ch < 0x80 && ISPRINT (ch))
- pp_character (pp, ch);
+ sink.add_character (ch);
else
{
char buf[16];
sprintf (buf, "<U+%04X>", ch);
- pp_string (pp, buf);
+ sink.add_text (buf);
}
}
char_display_policy result
(source_policy.get_column_policy ().get_tabstop (),
cpp_wcwidth,
- default_print_decoded_ch);
+ default_print_decoded_ch<to_text>,
+ default_print_decoded_ch<to_html>);
/* If the diagnostic suggests escaping non-ASCII bytes, then
use policy from user-supplied options. */
gcc_unreachable ();
case DIAGNOSTICS_ESCAPE_FORMAT_UNICODE:
result.m_width_cb = escape_as_unicode_width;
- result.m_print_cb = escape_as_unicode_print;
+ result.m_print_text_cb = escape_as_unicode_print<to_text>;
+ result.m_print_html_cb = escape_as_unicode_print<to_html>;
break;
case DIAGNOSTICS_ESCAPE_FORMAT_BYTES:
result.m_width_cb = escape_as_bytes_width;
- result.m_print_cb = escape_as_bytes_print;
+ result.m_print_text_cb = escape_as_bytes_print<to_text>;
+ result.m_print_html_cb = escape_as_bytes_print<to_html>;
break;
}
}
/* Print a line showing a gap in the line numbers, for showing the boundary
between two line spans. */
+template<>
void
-layout_printer::print_gap_in_line_numbering ()
+layout_printer<to_text>::print_gap_in_line_numbering ()
{
gcc_assert (m_layout.m_options.show_line_numbers_p);
- pp_emit_prefix (&m_pp);
+ m_sink.emit_text_prefix ();
for (int i = 0; i < m_layout.get_linenum_width () + 1; i++)
- pp_character (&m_pp, '.');
+ m_sink.add_character ('.');
- pp_newline (&m_pp);
+ m_sink.add_newline ();
+}
+
+template<>
+void
+layout_printer<to_html>::print_gap_in_line_numbering ()
+{
+ gcc_assert (m_layout.m_options.show_line_numbers_p);
+
+ m_sink.add_raw_html
+ ("<tbody class=\"line-span-jump\">\n"
+ "<tr class=\"line-span-jump-row\">"
+ "<td class=\"linenum-gap\">[...]</td>"
+ "<td class=\"source-gap\"/></tr>\n"
+ "</tbody>\n");
}
/* Return true iff we should print a heading when starting the
colorization and tab expansion, this function tracks the line position in
both byte and display column units. */
+template<typename Sink>
line_bounds
-layout_printer::print_source_line (linenum_type row, const char *line, int line_bytes)
+layout_printer<Sink>::print_source_line (linenum_type row,
+ const char *line,
+ int line_bytes)
{
- m_colorizer.set_normal_text ();
-
- pp_emit_prefix (&m_pp);
+ m_sink.colorize_text_ensure_normal ();
+ m_sink.push_html_tag ("tr", true);
+ m_sink.emit_text_prefix ();
if (m_layout.m_options.show_line_numbers_p)
{
+ m_sink.push_html_tag_with_class ("td", "linenum", true);
int width = num_digits (row);
for (int i = 0; i < m_layout.get_linenum_width () - width; i++)
- pp_space (&m_pp);
- pp_printf (&m_pp, "%i |", row);
+ m_sink.add_space ();
+ char buf[20];
+ sprintf (buf, "%i", row);
+ m_sink.add_text (buf);
+ if (Sink::is_text ())
+ m_sink.add_text (" |");
+ m_sink.pop_html_tag ("td");
}
+ m_sink.push_html_tag_with_class ("td", "left-margin", true);
print_leftmost_column ();
+ m_sink.pop_html_tag ("td");
/* We will stop printing the source line at any trailing whitespace. */
line_bytes = get_line_bytes_without_trailing_whitespace (line,
tab expansion, and for implementing m_x_offset_display. */
cpp_display_width_computation dw (line, line_bytes, m_layout.m_char_policy);
+ m_sink.push_html_tag_with_class ("td", "source", true);
+
/* Skip the first m_x_offset_display display columns. In case the leading
portion that will be skipped ends with a character with wcwidth > 1, then
it is possible we skipped too much, so account for that by padding with
for (int skipped_display_cols
= dw.advance_display_cols (m_layout.m_x_offset_display);
skipped_display_cols > m_layout.m_x_offset_display; --skipped_display_cols)
- pp_space (&m_pp);
+ m_sink.add_space ();
/* Print the line and compute the line_bounds. */
line_bounds lbounds;
CU_BYTES,
&state);
if (in_range_p)
- m_colorizer.set_range (state.range_idx);
+ m_sink.colorize_text_for_range_idx (state.range_idx);
else
- m_colorizer.set_normal_text ();
+ m_sink.colorize_text_ensure_normal ();
}
/* Get the display width of the next character to be output, expanding
/* The returned display width is the number of spaces into which the
tab should be expanded. */
for (int i = 0; i != this_display_width; ++i)
- pp_space (&m_pp);
+ m_sink.add_space ();
continue;
}
}
/* Output the character. */
- m_layout.m_char_policy.m_print_cb (&m_pp, cp);
+ m_sink.print_decoded_char (m_layout.m_char_policy, cp);
c = dw.next_byte ();
}
- print_newline ();
+ end_line ();
return lbounds;
}
/* Print the leftmost column after the margin, which is used for showing
links between labels (e.g. for CFG edges in execution paths). */
+template<typename Sink>
void
-layout_printer::print_leftmost_column ()
+layout_printer<Sink>::print_leftmost_column ()
{
if (!get_options ().show_event_links_p)
gcc_assert (m_link_lhs_state == link_lhs_state::none);
default:
gcc_unreachable ();
case link_lhs_state::none:
- pp_space (&m_pp);
+ m_sink.add_space ();
break;
case link_lhs_state::rewinding_to_lhs:
{
- m_colorizer.set_cfg_edge ();
+ m_sink.colorize_text_for_cfg_edge ();
const cppchar_t ch = get_theme ().get_cppchar
(text_art::theme::cell_kind::CFG_FROM_LEFT_TO_DOWN);
- pp_unicode_character (&m_pp, ch);
- m_colorizer.set_normal_text ();
+ m_sink.add_character (ch);
+ m_sink.colorize_text_ensure_normal ();
}
break;
case link_lhs_state::at_lhs:
{
- m_colorizer.set_cfg_edge ();
+ m_sink.colorize_text_for_cfg_edge ();
const cppchar_t ch = get_theme ().get_cppchar
(text_art::theme::cell_kind::CFG_DOWN);
- pp_unicode_character (&m_pp, ch);
- m_colorizer.set_normal_text ();
+ m_sink.add_character (ch);
+ m_sink.colorize_text_ensure_normal ();
}
break;
case link_lhs_state::indenting_to_dest:
{
- m_colorizer.set_cfg_edge ();
+ m_sink.colorize_text_for_cfg_edge ();
const cppchar_t ch = get_theme ().get_cppchar
(text_art::theme::cell_kind::CFG_FROM_DOWN_TO_RIGHT);
- pp_unicode_character (&m_pp, ch);
- m_colorizer.set_normal_text ();
+ m_sink.add_character (ch);
+ m_sink.colorize_text_ensure_normal ();
}
break;
}
}
-/* Begin an annotation line. If m_show_line_numbers_p, print the left
- margin, which is empty for annotation lines.
+/* Begin an annotation line for either text or html output
+
+ If m_show_line_numbers_p, print the left margin, which is empty
+ for annotation lines.
After any left margin, print a leftmost column, which is used for
- showing links between labels (e.g. for CFG edges in execution paths). */
+ showing links between labels (e.g. for CFG edges in execution paths).
+
+ For text sinks, this also first prints the text prefix.
+ For html sinks, this also pushes <tr> and <td> open tags, where the
+ <td> is for the coming annotations. */
+template<typename Sink>
void
-layout_printer::start_annotation_line (char margin_char)
+layout_printer<Sink>::start_annotation_line (enum margin_kind margin)
{
- pp_emit_prefix (&m_pp);
+ m_sink.emit_text_prefix ();
+ m_sink.push_html_tag ("tr", true);
+
+ char margin_char = (margin == margin_kind::insertion
+ ? '+'
+ : ' ');
+
if (get_options ().show_line_numbers_p)
{
/* Print the margin. If MARGIN_CHAR != ' ', then print up to 3
of it, right-aligned, padded with spaces. */
+ m_sink.push_html_tag_with_class ("td", "linenum", true);
int i;
for (i = 0; i < m_layout.m_linenum_width - 3; i++)
- pp_space (&m_pp);
+ m_sink.add_space ();
for (; i < m_layout.m_linenum_width; i++)
- pp_character (&m_pp, margin_char);
- pp_string (&m_pp, " |");
+ m_sink.add_character (margin_char);
+ if (Sink::is_text ())
+ m_sink.add_text (" |");
+ m_sink.pop_html_tag ("td");
}
- if (margin_char == ' ')
- print_leftmost_column ();
+
+ m_sink.push_html_tag_with_class ("td", "left-margin", true);
+ if (margin == margin_kind::insertion)
+ m_sink.add_character (margin_char);
else
- pp_character (&m_pp, margin_char);
+ print_leftmost_column ();
+ m_sink.pop_html_tag ("td");
+
+ m_sink.push_html_tag_with_class ("td",
+ (margin == margin_kind::ruler
+ ? "ruler"
+ : "annotation"),
+ true);
+}
+
+/* End a source or annotation line: text implementation.
+ Reset any colorization and emit a newline. */
+
+template<>
+void
+layout_printer<to_text>::end_line ()
+{
+ m_sink.colorize_text_ensure_normal ();
+ m_sink.add_newline ();
+}
+
+/* End a source or annotation line: HTML implementation.
+ Close the <td> and <tr> tags. */
+
+template<>
+void
+layout_printer<to_html>::end_line ()
+{
+ m_sink.pop_html_tag ("td");
+ m_sink.pop_html_tag ("tr");
}
/* Print a line consisting of the caret/underlines for the given
source line. */
+template<typename Sink>
void
-layout_printer::print_annotation_line (linenum_type row,
- const line_bounds lbounds)
+layout_printer<Sink>::print_annotation_line (linenum_type row,
+ const line_bounds lbounds)
{
int x_bound = m_layout.get_x_bound_for_row (row,
m_layout.m_exploc.m_display_col,
lbounds.m_last_non_ws_disp_col);
- start_annotation_line ();
+ start_annotation_line (margin_kind::normal);
for (int column = 1 + m_layout.m_x_offset_display; column < x_bound; column++)
{
if (in_range_p)
{
/* Within a range. Draw either the caret or an underline. */
- m_colorizer.set_range (state.range_idx);
+ m_sink.colorize_text_for_range_idx (state.range_idx);
if (state.draw_caret_p)
{
/* Draw the caret. */
caret_char = get_options ().caret_chars[state.range_idx];
else
caret_char = '^';
- pp_character (&m_pp, caret_char);
+ m_sink.add_character (caret_char);
}
else
- pp_character (&m_pp, '~');
+ m_sink.add_character ('~');
}
else
{
/* Not in a range. */
- m_colorizer.set_normal_text ();
- pp_character (&m_pp, ' ');
+ m_sink.colorize_text_ensure_normal ();
+ m_sink.add_character (' ');
}
}
- print_newline ();
+
+ end_line ();
}
/* A version of label_text that can live inside a vec.
class line_label
{
public:
- line_label (int state_idx, int column,
+ line_label (unsigned original_range_idx,
+ int state_idx, int column,
label_text text,
bool has_in_edge,
bool has_out_edge)
- : m_state_idx (state_idx), m_column (column),
+ : m_original_range_idx (original_range_idx),
+ m_state_idx (state_idx), m_column (column),
m_text (std::move (text)), m_label_line (0), m_has_vbar (true),
m_has_in_edge (has_in_edge),
m_has_out_edge (has_out_edge)
return -compare (ll1->m_state_idx, ll2->m_state_idx);
}
+ unsigned m_original_range_idx;
int m_state_idx;
int m_column;
pod_label_text m_text;
bool m_has_out_edge;
};
+template<>
+void
+layout_printer<to_text>::begin_label (const line_label &label)
+{
+ /* Colorize the text, unless it's for events in a
+ diagnostic_path. */
+ if (!m_is_diagnostic_path)
+ m_sink.colorize_text_for_range_idx (label.m_state_idx);
+}
+
+template<>
+void
+layout_printer<to_html>::begin_label (const line_label &)
+{
+ if (m_sink.m_html_label_writer)
+ m_sink.m_html_label_writer->begin_label ();
+}
+
+template<>
+void
+layout_printer<to_text>::end_label ()
+{
+ m_sink.colorize_text_ensure_normal ();
+}
+
+template<>
+void
+layout_printer<to_html>::end_label ()
+{
+ if (m_sink.m_html_label_writer)
+ m_sink.m_html_label_writer->end_label ();
+}
+
/* Print any labels in this row. */
+template <typename Sink>
void
-layout_printer::print_any_labels (linenum_type row)
+layout_printer<Sink>::print_any_labels (linenum_type row)
{
int i;
auto_vec<line_label> labels;
if (text.get () == NULL)
continue;
- labels.safe_push (line_label (i, disp_col, std::move (text),
+ labels.safe_push (line_label (range->m_original_idx,
+ i, disp_col, std::move (text),
range->has_in_edge (),
range->has_out_edge ()));
}
gcc_assert (get_options ().show_event_links_p);
m_link_lhs_state = link_lhs_state::indenting_to_dest;
}
- start_annotation_line ();
+ start_annotation_line (margin_kind::normal);
int column = 1 + m_layout.m_x_offset_display;
line_label *label;
. ^~~~~~~~~~~~~
. this text. */
gcc_assert (get_options ().show_event_links_p);
- m_colorizer.set_cfg_edge ();
+ m_sink.colorize_text_for_cfg_edge ();
const cppchar_t right= get_theme ().get_cppchar
(text_art::theme::cell_kind::CFG_RIGHT);
while (column < label->m_column - 1)
{
- pp_unicode_character (&m_pp, right);
+ m_sink.add_character (right);
column++;
}
if (column == label->m_column - 1)
{
- pp_character (&m_pp, '>');
+ m_sink.add_character ('>');
column++;
}
- m_colorizer.set_normal_text ();
+ m_sink.colorize_text_ensure_normal ();
m_link_lhs_state = link_lhs_state::none;
label_line_with_in_edge = -1;
}
else
move_to_column (&column, label->m_column, true);
gcc_assert (column == label->m_column);
- /* Colorize the text, unless it's for events in a
- diagnostic_path. */
- if (!m_is_diagnostic_path)
- m_colorizer.set_range (label->m_state_idx);
- pp_string (&m_pp, label->m_text.m_buffer);
- m_colorizer.set_normal_text ();
+
+ begin_label (*label);
+ m_sink.add_text (label->m_text.m_buffer);
+ end_label ();
+
column += label->m_display_width;
if (get_options ().show_event_links_p && label->m_has_out_edge)
{
(text_art::theme::cell_kind::CFG_RIGHT);
const cppchar_t from_right_to_down= get_theme ().get_cppchar
(text_art::theme::cell_kind::CFG_FROM_RIGHT_TO_DOWN);
- m_colorizer.set_cfg_edge ();
- pp_space (&m_pp);
- pp_unicode_character (&m_pp, right);
- pp_unicode_character (&m_pp, '>');
- pp_unicode_character (&m_pp, right);
- pp_unicode_character (&m_pp, from_right_to_down);
- m_colorizer.set_normal_text ();
+ m_sink.colorize_text_for_cfg_edge ();
+ m_sink.add_space ();
+ m_sink.add_character (right);
+ m_sink.add_character ('>');
+ m_sink.add_character (right);
+ m_sink.add_character (from_right_to_down);
+ m_sink.colorize_text_ensure_normal ();
column += 5;
m_link_rhs_column = column - 1;
}
{
gcc_assert (column <= label->m_column);
move_to_column (&column, label->m_column, true);
- m_colorizer.set_range (label->m_state_idx);
- pp_character (&m_pp, '|');
- m_colorizer.set_normal_text ();
+ m_sink.colorize_text_for_range_idx (label->m_state_idx);
+ m_sink.add_character ('|');
+ m_sink.colorize_text_ensure_normal ();
column++;
}
}
if (m_link_rhs_column != -1 && column < m_link_rhs_column)
{
move_to_column (&column, m_link_rhs_column, true);
- m_colorizer.set_cfg_edge ();
+ m_sink.colorize_text_for_cfg_edge ();
const cppchar_t down= get_theme ().get_cppchar
(text_art::theme::cell_kind::CFG_DOWN);
- pp_unicode_character (&m_pp, down);
- m_colorizer.set_normal_text ();
+ m_sink.add_character (down);
+ m_sink.colorize_text_ensure_normal ();
}
- print_newline ();
+ end_line ();
}
}
if (m_link_rhs_column != -1)
{
int column = 1 + m_layout.m_x_offset_display;
- start_annotation_line ();
+ start_annotation_line (margin_kind::normal);
move_to_column (&column, m_link_rhs_column, true);
- m_colorizer.set_cfg_edge ();
+ m_sink.colorize_text_for_cfg_edge ();
const cppchar_t down= get_theme ().get_cppchar
(text_art::theme::cell_kind::CFG_DOWN);
- pp_unicode_character (&m_pp, down);
- m_colorizer.set_normal_text ();
- print_newline ();
+ m_sink.add_character (down);
+ end_line ();
}
/* Clean up. */
They are printed on lines of their own, before the source line
itself, with a leading '+'. */
+template <typename Sink>
void
-layout_printer::print_leading_fixits (linenum_type row)
+layout_printer<Sink>::print_leading_fixits (linenum_type row)
{
for (unsigned int i = 0; i < m_layout.m_fixit_hints.length (); i++)
{
and the inserted line with "insert" colorization
helps them stand out from each other, and from
the surrounding text. */
- m_colorizer.set_normal_text ();
- start_annotation_line ('+');
- m_colorizer.set_fixit_insert ();
+ m_sink.colorize_text_ensure_normal ();
+ start_annotation_line (margin_kind::insertion);
+ m_sink.colorize_text_for_fixit_insert ();
/* Print all but the trailing newline of the fix-it hint.
We have to print the newline separately to avoid
getting additional pp prefixes printed. */
for (size_t i = 0; i < hint->get_length () - 1; i++)
- pp_character (&m_pp, hint->get_string ()[i]);
- m_colorizer.set_normal_text ();
- pp_newline (&m_pp);
+ m_sink.add_character (hint->get_string ()[i]);
+ end_line ();
}
}
}
Fix-it hints that insert new lines are handled separately,
in layout::print_leading_fixits. */
+template<typename Sink>
void
-layout_printer::print_trailing_fixits (linenum_type row)
+layout_printer<Sink>::print_trailing_fixits (linenum_type row)
{
/* Build a list of correction instances for the line,
potentially consolidating hints (for the sake of readability). */
int column = 1 + m_layout.m_x_offset_display;
if (!corrections.m_corrections.is_empty ())
- start_annotation_line ();
+ start_annotation_line (margin_kind::normal);
FOR_EACH_VEC_ELT (corrections.m_corrections, i, c)
{
/* This assumes the insertion just affects one line. */
int start_column = c->m_printed_columns.start;
move_to_column (&column, start_column, true);
- m_colorizer.set_fixit_insert ();
- pp_string (&m_pp, c->m_text);
- m_colorizer.set_normal_text ();
+ m_sink.colorize_text_for_fixit_insert ();
+ m_sink.add_text (c->m_text);
+ m_sink.colorize_text_ensure_normal ();
column += c->m_display_cols;
}
else
|| c->m_byte_length == 0)
{
move_to_column (&column, start_column, true);
- m_colorizer.set_fixit_delete ();
+ m_sink.colorize_text_for_fixit_delete ();
for (; column <= finish_column; column++)
- pp_character (&m_pp, '-');
- m_colorizer.set_normal_text ();
+ m_sink.add_character ('-');
+ m_sink.colorize_text_ensure_normal ();
}
/* Print the replacement text. REPLACE also covers
removals, so only do this extra work (potentially starting
if (c->m_byte_length > 0)
{
move_to_column (&column, start_column, true);
- m_colorizer.set_fixit_insert ();
- pp_string (&m_pp, c->m_text);
- m_colorizer.set_normal_text ();
+ m_sink.colorize_text_for_fixit_insert ();
+ m_sink.add_text (c->m_text);
+ m_sink.colorize_text_ensure_normal ();
column += c->m_display_cols;
}
}
}
+ if (!corrections.m_corrections.is_empty ())
+ m_sink.pop_html_tag ("td");
+
/* Add a trailing newline, if necessary. */
move_to_column (&column, 1 + m_layout.m_x_offset_display, false);
}
-/* Disable any colorization and emit a newline. */
-
-void
-layout_printer::print_newline ()
-{
- m_colorizer.set_normal_text ();
- pp_newline (&m_pp);
-}
-
/* Return true if (ROW/COLUMN) is within a range of the layout.
If it returns true, OUT_STATE is written to, with the
range index, and whether we should draw the caret at
and updating *COLUMN. If ADD_LEFT_MARGIN, then print the (empty)
left margin after any newline. */
+template<typename Sink>
void
-layout_printer::move_to_column (int *column,
- int dest_column,
- bool add_left_margin)
+layout_printer<Sink>::move_to_column (int *column,
+ int dest_column,
+ bool add_left_margin)
{
/* Start a new line if we need to. */
if (*column > dest_column)
{
- print_newline ();
+ end_line ();
if (add_left_margin)
- start_annotation_line ();
+ start_annotation_line (margin_kind::normal);
*column = 1 + m_layout.m_x_offset_display;
}
while (*column < dest_column)
{
/* For debugging column issues, it can be helpful to replace this
- pp_space call with
- pp_character (&m_pp, '0' + (*column % 10));
+ add_space call with
+ m_sink.add_character ('0' + (*column % 10));
to visualize the changing value of "*column". */
- pp_space (&m_pp);
+ m_sink.add_space ();
(*column)++;
}
}
/* For debugging layout issues, render a ruler giving column numbers
(after the 1-column indent). */
+template<typename Sink>
void
-layout_printer::show_ruler (int max_column)
+layout_printer<Sink>::show_ruler (int max_column)
{
+ m_sink.push_html_tag_with_class("thead", "ruler", false);
+
/* Hundreds. */
if (max_column > 99)
{
- start_annotation_line ();
+ start_annotation_line (margin_kind::ruler);
for (int column = 1 + m_layout.m_x_offset_display;
column <= max_column;
++column)
if (column % 10 == 0)
- pp_character (&m_pp, '0' + (column / 100) % 10);
+ m_sink.add_character ('0' + (column / 100) % 10);
else
- pp_space (&m_pp);
- pp_newline (&m_pp);
+ m_sink.add_space ();
+ end_line ();
}
/* Tens. */
- start_annotation_line ();
+ start_annotation_line (margin_kind::ruler);
for (int column = 1 + m_layout.m_x_offset_display;
column <= max_column;
++column)
if (column % 10 == 0)
- pp_character (&m_pp, '0' + (column / 10) % 10);
+ m_sink.add_character ('0' + (column / 10) % 10);
else
- pp_space (&m_pp);
- pp_newline (&m_pp);
+ m_sink.add_space ();
+ end_line ();
/* Units. */
- start_annotation_line ();
+ start_annotation_line (margin_kind::ruler);
for (int column = 1 + m_layout.m_x_offset_display;
column <= max_column;
++column)
- pp_character (&m_pp, '0' + (column % 10));
- pp_newline (&m_pp);
+ m_sink.add_character ('0' + (column % 10));
+ end_line ();
+
+ m_sink.pop_html_tag("thead"); // thead
}
/* Print leading fix-its (for new lines inserted before the source line)
then the source line, followed by an annotation line
consisting of any caret/underlines, then any fixits.
If the source line can't be read, print nothing. */
+template<typename Sink>
void
-layout_printer::print_line (linenum_type row)
+layout_printer<Sink>::print_line (linenum_type row)
{
char_span line
= m_layout.m_file_cache.get_source_line (m_layout.m_exploc.file, row);
showing the link entering at the top right and emerging
at the bottom left. */
+template<typename Sink>
void
-layout_printer::print_any_right_to_left_edge_lines ()
+layout_printer<Sink>::print_any_right_to_left_edge_lines ()
{
if (m_link_rhs_column == -1)
/* Can also happen if the out-edge had UNKNOWN_LOCATION. */
gcc_assert (get_options ().show_event_links_p);
/* Print the line with "|". */
- start_annotation_line ();
+ start_annotation_line (margin_kind::normal);
+
int column = 1 + m_layout.m_x_offset_display;
move_to_column (&column, m_link_rhs_column, true);
- m_colorizer.set_cfg_edge ();
+ m_sink.colorize_text_for_cfg_edge ();
const cppchar_t down= get_theme ().get_cppchar
(text_art::theme::cell_kind::CFG_DOWN);
- pp_unicode_character (&m_pp, down);
- m_colorizer.set_normal_text ();
- pp_newline (&m_pp);
+ m_sink.add_character (down);
+ end_line ();
/* Print the line with "┌──────────────────────────────────────────┘". */
m_link_lhs_state = link_lhs_state::rewinding_to_lhs;
- start_annotation_line ();
- m_colorizer.set_cfg_edge ();
+ start_annotation_line (margin_kind::normal);
+ m_sink.colorize_text_for_cfg_edge ();
const cppchar_t left= get_theme ().get_cppchar
(text_art::theme::cell_kind::CFG_LEFT);
for (int column = 1 + m_layout.m_x_offset_display;
column < m_link_rhs_column;
++column)
- pp_unicode_character (&m_pp, left);
+ m_sink.add_character (left);
const cppchar_t from_down_to_left = get_theme ().get_cppchar
(text_art::theme::cell_kind::CFG_FROM_DOWN_TO_LEFT);
- pp_unicode_character (&m_pp, from_down_to_left);
- m_colorizer.set_normal_text ();
- pp_newline (&m_pp);
+ m_sink.add_character (from_down_to_left);
+ end_line ();
/* We now have a link line on the LHS,
and no longer have one on the RHS. */
m_link_rhs_column = -1;
}
-layout_printer::layout_printer (pretty_printer &pp,
- const layout &layout,
- const rich_location &richloc,
- diagnostic_t diagnostic_kind)
-: m_pp (pp),
+template<typename Sink>
+layout_printer<Sink>::layout_printer (Sink &sink,
+ const layout &layout,
+ bool is_diagnostic_path)
+: m_sink (sink),
m_layout (layout),
- m_colorizer (m_pp, richloc, diagnostic_kind),
- m_is_diagnostic_path (diagnostic_kind == DK_DIAGNOSTIC_PATH),
+ m_is_diagnostic_path (is_diagnostic_path),
m_link_lhs_state (link_lhs_state::none),
m_link_rhs_column (-1)
{
source_policy.print (pp, richloc, diagnostic_kind, effects);
}
+/* As above, but print in HTML form to XP.
+ If non-null, use LABEL_WRITER when writing labelled ranges. */
+
+void
+diagnostic_context::maybe_show_locus_as_html (const rich_location &richloc,
+ const diagnostic_source_printing_options &opts,
+ diagnostic_t diagnostic_kind,
+ xml::printer &xp,
+ diagnostic_source_effect_info *effects,
+ html_label_writer *label_writer)
+{
+ const location_t loc = richloc.get_loc ();
+ /* Do nothing if source-printing has been disabled. */
+ if (!opts.enabled)
+ return;
+
+ /* Don't attempt to print source for UNKNOWN_LOCATION and for builtins. */
+ if (loc <= BUILTINS_LOCATION)
+ return;
+
+ /* Don't print the same source location twice in a row, unless we have
+ fix-it hints, or multiple locations, or a label. */
+ if (loc == m_last_location
+ && richloc.get_num_fixit_hints () == 0
+ && richloc.get_num_locations () == 1
+ && richloc.get_range (0)->m_label == NULL)
+ return;
+
+ m_last_location = loc;
+
+ diagnostic_source_print_policy source_policy (*this, opts);
+ source_policy.print_as_html (xp, richloc, diagnostic_kind, effects,
+ label_writer);
+}
+
diagnostic_source_print_policy::
diagnostic_source_print_policy (const diagnostic_context &dc)
: m_options (dc.m_source_printing),
m_location_policy (dc),
- m_start_span_cb (dc.m_text_callbacks.m_start_span),
+ m_text_start_span_cb (dc.m_text_callbacks.m_text_start_span),
+ m_html_start_span_cb (dc.m_text_callbacks.m_html_start_span),
m_file_cache (dc.get_file_cache ()),
m_diagram_theme (dc.get_diagram_theme ()),
m_escape_format (dc.get_escape_format ())
const diagnostic_source_printing_options &opts)
: m_options (opts),
m_location_policy (dc),
- m_start_span_cb (dc.m_text_callbacks.m_start_span),
+ m_text_start_span_cb (dc.m_text_callbacks.m_text_start_span),
+ m_html_start_span_cb (dc.m_text_callbacks.m_html_start_span),
m_file_cache (dc.get_file_cache ()),
m_diagram_theme (dc.get_diagram_theme ()),
m_escape_format (dc.get_escape_format ())
const
{
layout layout (*this, richloc, effects);
- layout_printer lp (pp, layout, richloc, diagnostic_kind);
+ colorizer col (pp, richloc, diagnostic_kind);
+ to_text sink (pp, col);
+ layout_printer<to_text> lp (sink, layout,
+ diagnostic_kind == DK_DIAGNOSTIC_PATH);
lp.print (*this);
}
+/* As above, but print in HTML form to XP.
+ If non-null, use LABEL_WRITER when writing labelled ranges. */
+
void
-layout_printer::print (const diagnostic_source_print_policy &source_policy)
+diagnostic_source_print_policy::print_as_html (xml::printer &xp,
+ const rich_location &richloc,
+ diagnostic_t diagnostic_kind,
+ diagnostic_source_effect_info *effects,
+ html_label_writer *label_writer)
+ const
{
- diagnostic_prefixing_rule_t saved_rule = pp_prefixing_rule (&m_pp);
- pp_prefixing_rule (&m_pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
+ layout layout (*this, richloc, effects);
+ to_html sink (xp, label_writer);
+ layout_printer<to_html> lp (sink, layout,
+ diagnostic_kind == DK_DIAGNOSTIC_PATH);
+ lp.print (*this);
+}
+
+template <typename Sink>
+void
+layout_printer<Sink>::print (const diagnostic_source_print_policy &source_policy)
+{
+ m_sink.push_html_tag_with_class ("table", "locus", false);
if (get_options ().show_ruler_p)
show_ruler (m_layout.m_x_offset_display + get_options ().max_width);
= m_layout.get_expanded_location (line_span);
const diagnostic_location_print_policy &
loc_policy = source_policy.get_location_policy ();
- source_policy.get_start_span_fn () (loc_policy, &m_pp, exploc);
+ m_sink.invoke_start_span_fn (source_policy, loc_policy, exploc);
}
}
+
+ m_sink.push_html_tag_with_class ("tbody", "line-span", false);
+
/* Iterate over the lines within this span (using linenum_arith_t to
avoid overflow with 0xffffffff causing an infinite loop). */
linenum_arith_t last_line = line_span->get_last_line ();
for (linenum_arith_t row = line_span->get_first_line ();
row <= last_line; row++)
print_line (row);
+
+ m_sink.pop_html_tag ("tbody");
}
if (auto effect_info = m_layout.m_effect_info)
effect_info->m_trailing_out_edge_column = m_link_rhs_column;
- pp_prefixing_rule (&m_pp) = saved_rule;
+ m_sink.pop_html_tag ("table");
}
#if CHECKING_P
namespace selftest {
+static std::unique_ptr<xml::node>
+make_element_for_locus (const rich_location &rich_loc,
+ diagnostic_t kind,
+ diagnostic_context &dc)
+{
+ dc.m_last_location = UNKNOWN_LOCATION;
+
+ xml::element wrapper ("wrapper", false);
+ xml::printer xp (wrapper);
+ dc.maybe_show_locus_as_html (rich_loc,
+ dc.m_source_printing,
+ kind,
+ xp,
+ nullptr,
+ nullptr); // label_writer
+ if (wrapper.m_children.size () > 0)
+ return std::move (wrapper.m_children[0]);
+ else
+ return nullptr;
+}
+
+static label_text
+make_raw_html_for_locus (const rich_location &rich_loc,
+ diagnostic_t kind,
+ diagnostic_context &dc)
+{
+ auto node = make_element_for_locus (rich_loc, kind, dc);
+ pretty_printer pp;
+ if (node)
+ node->write_as_xml (&pp, 0, true);
+ return label_text::take (xstrdup (pp_formatted_text (&pp)));
+}
+
/* Selftests for diagnostic_show_locus. */
diagnostic_show_locus_fixture::
linemap_position_for_column (line_table,
emoji_col));
layout test_layout (policy, richloc, nullptr);
- layout_printer lp (*dc.get_reference_printer (), test_layout, richloc, DK_ERROR);
+ colorizer col (*dc.get_reference_printer (),
+ richloc, DK_ERROR);
+ to_text sink (*dc.get_reference_printer (), col);
+ layout_printer<to_text> lp (sink, test_layout, false);
lp.print (policy);
ASSERT_STREQ (" | 1 \n"
" | 1 \n"
linemap_position_for_column (line_table,
emoji_col + 2));
layout test_layout (dc, richloc, nullptr);
- layout_printer lp (*dc.get_reference_printer (), test_layout, richloc, DK_ERROR);
+ colorizer col (*dc.get_reference_printer (),
+ richloc, DK_ERROR);
+ to_text sink (*dc.get_reference_printer (), col);
+ layout_printer<to_text> lp (sink, test_layout, false);
lp.print (policy);
ASSERT_STREQ (" | 1 1 \n"
" | 1 2 \n"
dc.m_tabstop = tabstop;
diagnostic_source_print_policy policy (dc);
layout test_layout (policy, richloc, nullptr);
- layout_printer lp (*dc.get_reference_printer (), test_layout, richloc, DK_ERROR);
+ colorizer col (*dc.get_reference_printer (),
+ richloc, DK_ERROR);
+ to_text sink (*dc.get_reference_printer (), col);
+ layout_printer<to_text> lp (sink, test_layout, false);
lp.print (policy);
const char *out = pp_formatted_text (dc.get_reference_printer ());
ASSERT_EQ (NULL, strchr (out, '\t'));
dc.m_source_printing.show_line_numbers_p = true;
diagnostic_source_print_policy policy (dc);
layout test_layout (policy, richloc, nullptr);
- layout_printer lp (*dc.get_reference_printer (), test_layout, richloc, DK_ERROR);
+ colorizer col (*dc.get_reference_printer (),
+ richloc, DK_ERROR);
+ to_text sink (*dc.get_reference_printer (), col);
+ layout_printer<to_text> lp (sink, test_layout, false);
lp.print (policy);
/* We have arranged things so that two columns will be printed before
ASSERT_STREQ (" foo = bar.field;\n"
" ~~~^~~~~~\n",
dc.test_show_locus (richloc));
+
+ {
+ test_diagnostic_context dc;
+ auto out = make_raw_html_for_locus (richloc, DK_ERROR, dc);
+ ASSERT_STREQ
+ ("<table class=\"locus\">\n"
+ " <tbody class=\"line-span\">\n"
+ " <tr><td class=\"left-margin\"> </td><td class=\"source\">foo = bar.field;</td></tr>\n"
+ " <tr><td class=\"left-margin\"> </td><td class=\"annotation\"> ~~~^~~~~~</td></tr>\n"
+ " </tbody>\n"
+ "</table>\n",
+ out.get ());
+ }
+ {
+ test_diagnostic_context dc;
+ dc.m_source_printing.show_line_numbers_p = true;
+ auto out = make_raw_html_for_locus (richloc, DK_ERROR, dc);
+ ASSERT_STREQ
+ ("<table class=\"locus\">\n"
+ " <tbody class=\"line-span\">\n"
+ " <tr><td class=\"linenum\"> 1</td><td class=\"left-margin\"> </td><td class=\"source\">foo = bar.field;</td></tr>\n"
+ " <tr><td class=\"linenum\"> </td><td class=\"left-margin\"> </td><td class=\"annotation\"> ~~~^~~~~~</td></tr>\n"
+ " </tbody>\n"
+ "</table>\n",
+ out.get ());
+ }
}
/* Multiple ranges and carets. */
" | label 1\n"
" label 0\n",
dc.test_show_locus (richloc));
+
+ {
+ test_diagnostic_context dc;
+ dc.m_source_printing.show_line_numbers_p = true;
+ auto out = make_raw_html_for_locus (richloc, DK_ERROR, dc);
+ ASSERT_STREQ
+ ("<table class=\"locus\">\n"
+ " <tbody class=\"line-span\">\n"
+ " <tr><td class=\"linenum\"> 1</td><td class=\"left-margin\"> </td><td class=\"source\">foo = bar.field;</td></tr>\n"
+ " <tr><td class=\"linenum\"> </td><td class=\"left-margin\"> </td><td class=\"annotation\">^~~ ~~~ ~~~~~</td></tr>\n"
+ " <tr><td class=\"linenum\"> </td><td class=\"left-margin\"> </td><td class=\"annotation\">| | |</td></tr>\n"
+ " <tr><td class=\"linenum\"> </td><td class=\"left-margin\"> </td><td class=\"annotation\">| | label 2</td></tr>\n"
+ " <tr><td class=\"linenum\"> </td><td class=\"left-margin\"> </td><td class=\"annotation\">| label 1</td></tr>\n"
+ " <tr><td class=\"linenum\"> </td><td class=\"left-margin\"> </td><td class=\"annotation\">label 0</td></tr>\n"
+ " </tbody>\n"
+ "</table>\n",
+ out.get ());
+ }
}
/* Example of boundary conditions: label 0 and 1 have just enough clearance,
m_internal_error = nullptr;
m_adjust_diagnostic_info = nullptr;
m_text_callbacks.m_begin_diagnostic = default_diagnostic_text_starter;
- m_text_callbacks.m_start_span = default_diagnostic_start_span_fn;
+ m_text_callbacks.m_text_start_span
+ = default_diagnostic_start_span_fn<to_text>;
+ m_text_callbacks.m_html_start_span
+ = default_diagnostic_start_span_fn<to_html>;
m_text_callbacks.m_end_diagnostic = default_diagnostic_text_finalizer;
m_option_mgr = nullptr;
m_urlifier_stack = new auto_vec<urlifier_stack_node> ();
}
}
-void
-default_diagnostic_start_span_fn (const diagnostic_location_print_policy &loc_policy,
- pretty_printer *pp,
- expanded_location exploc)
-{
- const diagnostic_column_policy &column_policy
- = loc_policy.get_column_policy ();
- label_text text
- = column_policy.get_location_text (exploc,
- loc_policy.show_column_p (),
- pp_show_color (pp));
- pp_string (pp, text.get ());
- pp_newline (pp);
-}
-
/* Interface to specify diagnostic kind overrides. Returns the
previous setting, or DK_UNSPECIFIED if the parameters are out of
range. If OPTION_ID is zero, the new setting is for all the
class theme;
} // namespace text_art
+namespace xml
+{
+ class printer;
+} // namespace xml
+
/* An enum for controlling what units to use for the column number
when diagnostics are output, used by the -fdiagnostics-column-unit option.
Tabs will be expanded or not according to the value of -ftabstop. The origin
typedef void (*diagnostic_text_starter_fn) (diagnostic_text_output_format &,
const diagnostic_info *);
-typedef void
-(*diagnostic_start_span_fn) (const diagnostic_location_print_policy &,
- pretty_printer *,
- expanded_location);
+struct to_text;
+struct to_html;
+
+extern pretty_printer *get_printer (to_text &);
+
+template <typename Sink>
+using diagnostic_start_span_fn = void (*) (const diagnostic_location_print_policy &,
+ Sink &sink,
+ expanded_location);
typedef void (*diagnostic_text_finalizer_fn) (diagnostic_text_output_format &,
const diagnostic_info *,
const diagnostic_column_policy &
get_column_policy () const { return m_column_policy; }
+ void
+ print_text_span_start (const diagnostic_context &dc,
+ pretty_printer &pp,
+ const expanded_location &exploc);
+
+ void
+ print_html_span_start (const diagnostic_context &dc,
+ xml::printer &xp,
+ const expanded_location &exploc);
+
private:
diagnostic_column_policy m_column_policy;
bool m_show_column;
};
+/* Abstract base class for optionally supplying extra tags when writing
+ out annotation labels in HTML output. */
+
+class html_label_writer
+{
+public:
+ virtual ~html_label_writer () {}
+ virtual void begin_label () = 0;
+ virtual void end_label () = 0;
+};
+
/* A bundle of state for printing source within a diagnostic,
to isolate the interactions between diagnostic_context and the
implementation of diagnostic_show_locus. */
diagnostic_t diagnostic_kind,
diagnostic_source_effect_info *effect_info) const;
+ void
+ print_as_html (xml::printer &xp,
+ const rich_location &richloc,
+ diagnostic_t diagnostic_kind,
+ diagnostic_source_effect_info *effect_info,
+ html_label_writer *label_writer) const;
+
const diagnostic_source_printing_options &
get_options () const { return m_options; }
- diagnostic_start_span_fn
- get_start_span_fn () const { return m_start_span_cb; }
+ diagnostic_start_span_fn<to_text>
+ get_text_start_span_fn () const { return m_text_start_span_cb; }
+
+ diagnostic_start_span_fn<to_html>
+ get_html_start_span_fn () const { return m_html_start_span_cb; }
file_cache &
get_file_cache () const { return m_file_cache; }
private:
const diagnostic_source_printing_options &m_options;
class diagnostic_location_print_policy m_location_policy;
- diagnostic_start_span_fn m_start_span_cb;
+ diagnostic_start_span_fn<to_text> m_text_start_span_cb;
+ diagnostic_start_span_fn<to_html> m_html_start_span_cb;
file_cache &m_file_cache;
/* Other data copied from diagnostic_context. */
/* Give access to m_text_callbacks. */
friend diagnostic_text_starter_fn &
diagnostic_text_starter (diagnostic_context *context);
- friend diagnostic_start_span_fn &
+ friend diagnostic_start_span_fn<to_text> &
diagnostic_start_span (diagnostic_context *context);
friend diagnostic_text_finalizer_fn &
diagnostic_text_finalizer (diagnostic_context *context);
diagnostic_t diagnostic_kind,
pretty_printer &pp,
diagnostic_source_effect_info *effect_info);
+ void maybe_show_locus_as_html (const rich_location &richloc,
+ const diagnostic_source_printing_options &opts,
+ diagnostic_t diagnostic_kind,
+ xml::printer &xp,
+ diagnostic_source_effect_info *effect_info,
+ html_label_writer *label_writer);
void emit_diagram (const diagnostic_diagram &diagram);
/* This function is called by diagnostic_show_locus in between
disjoint spans of source code, so that the context can print
something to indicate that a new span of source code has begun. */
- diagnostic_start_span_fn m_start_span;
+ diagnostic_start_span_fn<to_text> m_text_start_span;
+ diagnostic_start_span_fn<to_html> m_html_start_span;
/* This function is called after the diagnostic message is printed. */
diagnostic_text_finalizer_fn m_end_diagnostic;
/* Client supplied function called between disjoint spans of source code,
so that the context can print
something to indicate that a new span of source code has begun. */
-inline diagnostic_start_span_fn &
+inline diagnostic_start_span_fn<to_text> &
diagnostic_start_span (diagnostic_context *context)
{
- return context->m_text_callbacks.m_start_span;
+ return context->m_text_callbacks.m_text_start_span;
}
/* Client supplied function called after a diagnostic message is
context->maybe_show_locus (*richloc, opts, diagnostic_kind, *pp, effect_info);
}
+inline void
+diagnostic_show_locus_as_html (diagnostic_context *context,
+ const diagnostic_source_printing_options &opts,
+ rich_location *richloc,
+ diagnostic_t diagnostic_kind,
+ xml::printer &xp,
+ diagnostic_source_effect_info *effect_info = nullptr,
+ html_label_writer *label_writer = nullptr)
+{
+ gcc_assert (context);
+ gcc_assert (richloc);
+ context->maybe_show_locus_as_html (*richloc, opts, diagnostic_kind, xp,
+ effect_info, label_writer);
+}
+
/* Because we read source files a second time after the frontend did it the
first time, we need to know how the frontend handled things like character
set conversion and UTF-8 BOM stripping, in order to make everything
#endif
void default_diagnostic_text_starter (diagnostic_text_output_format &,
const diagnostic_info *);
+template <typename Sink>
void default_diagnostic_start_span_fn (const diagnostic_location_print_policy &,
- pretty_printer *,
+ Sink &sink,
expanded_location);
void default_diagnostic_text_finalizer (diagnostic_text_output_format &,
const diagnostic_info *,
@item experimental-html
Emit diagnostics to a file in HTML format. This scheme is experimental,
-and may go away in future GCC releases. The details of the output are
-also subject to change.
+and may go away in future GCC releases. The keys and details of the output
+are also subject to change.
Supported keys are:
@table @gcctabopt
+@item css=@r{[}yes@r{|}no@r{]}
+Add an embedded <style> to the generated HTML. Defaults to yes.
+
@item file=@var{FILENAME}
Specify the filename to write the HTML output to, potentially with a
leading absolute or relative path. If not specified, it defaults to
@file{@var{source}.html}.
+@item javascript=@r{[}yes@r{|}no@r{]}
+Add an embedded <script> to the generated HTML providing a barebones UI
+for viewing results. Defaults to yes.
+
@end table
@end table
static void
gfc_diagnostic_start_span (const diagnostic_location_print_policy &loc_policy,
- pretty_printer *pp,
+ to_text &sink,
expanded_location exploc)
{
+ pretty_printer *pp = get_printer (sink);
const bool colorize = pp_show_color (pp);
char *locus_prefix
= gfc_diagnostic_build_locus_prefix (loc_policy, exploc, colorize);
const char *unparsed_arg,
const scheme_name_and_params &parsed_arg) const
{
+ bool css = true;
label_text filename;
+ bool javascript = true;
for (auto& iter : parsed_arg.m_kvs)
{
const std::string &key = iter.first;
const std::string &value = iter.second;
+ if (key == "css")
+ {
+ if (!parse_bool_value (ctxt, unparsed_arg, key, value,
+ css))
+ return nullptr;
+ continue;
+ }
if (key == "file")
{
filename = label_text::take (xstrdup (value.c_str ()));
continue;
}
+ if (key == "javascript")
+ {
+ if (!parse_bool_value (ctxt, unparsed_arg, key, value,
+ javascript))
+ return nullptr;
+ continue;
+ }
/* Key not found. */
auto_vec<const char *> known_keys;
+ known_keys.safe_push ("css");
known_keys.safe_push ("file");
- ctxt.report_unknown_key (unparsed_arg, key, get_scheme_name (), known_keys);
+ known_keys.safe_push ("javascript");
+ ctxt.report_unknown_key (unparsed_arg, key, get_scheme_name (),
+ known_keys);
return nullptr;
}
if (!output_file)
return nullptr;
+ html_generation_options html_gen_opts;
+ html_gen_opts.m_css = css;
+ html_gen_opts.m_javascript = javascript;
+
auto sink = make_html_sink (ctxt.m_dc,
*line_table,
+ html_gen_opts,
std::move (output_file));
return sink;
}
void
test_diagnostic_context::
start_span_cb (const diagnostic_location_print_policy &loc_policy,
- pretty_printer *pp,
+ to_text &sink,
expanded_location exploc)
{
exploc.file = "FILENAME";
- default_diagnostic_start_span_fn (loc_policy, pp, exploc);
+ default_diagnostic_start_span_fn<to_text> (loc_policy, sink, exploc);
}
bool
real filename (to avoid printing the names of tempfiles). */
static void
start_span_cb (const diagnostic_location_print_policy &,
- pretty_printer *,
+ to_text &sink,
expanded_location exploc);
/* Report a diagnostic to this context. For a selftest, this
/* { dg-do compile } */
-/* { dg-options "-fdiagnostics-add-output=experimental-html" } */
+/* { dg-options "-fdiagnostics-add-output=experimental-html:javascript=no" } */
/* Verify that basics of HTML output work. */
def html_tree():
return html_tree_from_env()
-XHTML = 'http://www.w3.org/1999/xhtml'
-ns = {'xhtml': XHTML}
-
-def make_tag(local_name):
- return f'{{{XHTML}}}' + local_name
-
def test_basics(html_tree):
root = html_tree.getroot ()
assert root.tag == make_tag('html')
/* { dg-do compile } */
-/* { dg-options "-fdiagnostics-set-output=experimental-html" } */
-/* { dg-additional-options "-fdiagnostics-show-caret" } */
+/* { dg-options "-fdiagnostics-set-output=experimental-html:javascript=no" } */
+/* { dg-additional-options "-fdiagnostics-show-caret -fdiagnostics-show-line-numbers" } */
extern char *gets (char *s);
def html_tree():
return html_tree_from_env()
-XHTML = 'http://www.w3.org/1999/xhtml'
-ns = {'xhtml': XHTML}
-
-def make_tag(local_name):
- return f'{{{XHTML}}}' + local_name
-
def test_metadata(html_tree):
root = html_tree.getroot ()
assert root.tag == make_tag('html')
assert metadata[1][0].text == 'STR34-C'
assert metadata[1][0].tail == ']'
- src = diag.find('xhtml:pre', ns)
- assert src.attrib['class'] == 'gcc-annotated-source'
- assert src.text == (
- ' gets (buf);\n'
- ' ^~~~~~~~~~\n')
+ src = diag.find('xhtml:table', ns)
+ assert src.attrib['class'] == 'locus'
+
+ tbody = src.find('xhtml:tbody', ns)
+ assert tbody.attrib['class'] == 'line-span'
+
+ rows = tbody.findall('xhtml:tr', ns)
+
+ quoted_src_tr = rows[0]
+ assert_quoted_line(quoted_src_tr,
+ ' 10', ' gets (buf);')
+
+ annotation_tr = rows[1]
+ assert_annotation_line(annotation_tr,
+ ' ^~~~~~~~~~')
# For reference, here's the generated HTML:
"""
<div class="gcc-diagnostic-list">
<div class="gcc-diagnostic">
<span class="gcc-message">never use '<span class="gcc-quoted-text">gets</span>'</span>
- <span class="gcc-metadata"><span class="gcc-metadata-item">[<a href="https://cwe.mitre.org/data/definitions/242.html">CWE-242</a>]</span><span class="gcc-metadata-item">[<a href="https://example.com/">STR34-C</a>]</span></span>
- ...etc...
+ <span class="gcc-metadata"><span class="gcc-metadata-item">[<a href="https://cwe.mitre.org/data/definitions/242.html">CWE-242</a>]</span><span class="gcc-metadata-item">[<a href="https://example.com/">STR34-C</a>]</span></span><table class="locus">
+<tbody class="line-span">
+<tr><td class="linenum"> 10</td> <td class="source"> gets (buf);</td></tr>
+<tr><td class="linenum"/><td class="annotation"> ^~~~~~~~~~</td></tr>
+</tbody>
+</table>
+
</div>
</div>
</body>
/* { dg-do compile } */
-/* { dg-options "-fdiagnostics-show-caret -fdiagnostics-show-line-numbers -fdiagnostics-path-format=inline-events -fdiagnostics-add-output=experimental-html" } */
+/* { dg-options "-fdiagnostics-show-caret -fdiagnostics-show-line-numbers -fdiagnostics-path-format=inline-events -fdiagnostics-add-output=experimental-html:javascript=no" } */
#include <stddef.h>
#include <stdlib.h>
def html_tree():
return html_tree_from_env()
-XHTML = 'http://www.w3.org/1999/xhtml'
-ns = {'xhtml': XHTML}
-
-def make_tag(local_name):
- return f'{{{XHTML}}}' + local_name
-
def test_paths(html_tree):
root = html_tree.getroot ()
assert root.tag == make_tag('html')
assert diag is not None
assert diag.attrib['class'] == 'gcc-diagnostic'
- pre = diag.findall('xhtml:pre', ns)
- assert pre[0].attrib['class'] == 'gcc-annotated-source'
- assert pre[1].attrib['class'] == 'gcc-execution-path'
- assert pre[1].text.startswith(" 'make_a_list_of_random_ints_badly': events 1-3")
+ event_ranges = diag.find('xhtml:div', ns)
+ assert_class(event_ranges, 'event-ranges')
+
+ frame_margin = event_ranges.find('xhtml:table', ns)
+ assert_class(frame_margin, 'stack-frame-with-margin')
+
+ tr = frame_margin.find('xhtml:tr', ns)
+ assert tr is not None
+ tds = tr.findall('xhtml:td', ns)
+ assert len(tds) == 2
+
+ assert_class(tds[0], 'interprocmargin')
+
+ test_frame = tds[1]
+ assert_frame(test_frame, 'make_a_list_of_random_ints_badly')
+ assert_event_range_with_margin(test_frame[1])
/* { dg-do compile } */
-/* { dg-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret -fdiagnostics-show-line-numbers" } */
+/* { dg-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret -fdiagnostics-show-line-numbers -fdiagnostics-add-output=experimental-html:javascript=no" } */
/* { dg-enable-nn-line-numbers "" } */
#include <stdio.h>
| | (9) calling 'fprintf'
|
{ dg-end-multiline-output "" } */
+
+/* Use a Python script to verify various properties about the generated
+ HTML file:
+ { dg-final { run-html-pytest diagnostic-test-paths-4.c "diagnostic-test-paths-4.py" } } */
--- /dev/null
+# Verify that interprocedural execution paths work in HTML output.
+
+from htmltest import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def html_tree():
+ return html_tree_from_env()
+
+def test_paths(html_tree):
+ diag = get_diag_by_index(html_tree, 0)
+ src = get_locus_within_diag (diag)
+
+ tbody = src.find('xhtml:tbody', ns)
+ assert_class(tbody, 'line-span')
+
+ rows = tbody.findall('xhtml:tr', ns)
+
+ quoted_src_tr = rows[0]
+ assert_quoted_line(quoted_src_tr,
+ ' 13', ' fprintf(stderr, "LOG: %s", msg); /* { dg-warning "call to \'fprintf\' from within signal handler" } */')
+
+ annotation_tr = rows[1]
+ assert_annotation_line(annotation_tr,
+ ' ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
+
+ event_ranges = diag.find('xhtml:div', ns)
+ assert_class(event_ranges, 'event-ranges')
+
+ test_frame_margin = event_ranges.find('xhtml:table', ns)
+ assert_class(test_frame_margin, 'stack-frame-with-margin')
+
+ tr = test_frame_margin.find('xhtml:tr', ns)
+ assert tr is not None
+ tds = tr.findall('xhtml:td', ns)
+ assert len(tds) == 2
+
+ assert_class(tds[0], 'interprocmargin')
+
+ test_frame = tds[1]
+ assert_frame(test_frame, 'test')
+ assert_event_range_with_margin(test_frame[1])
+
+# For reference, generated HTML looks like this:
+"""
+<table class="stack-frame-with-margin"><tr>
+ <td class="interprocmargin" style="padding-left: 100px"/>
+ <td class="stack-frame">
+<div class="frame-funcname"><span>test</span></div><table class="event-range-with-margin"><tr>
+ <td class="event-range">
+ <div class="events-hdr"><span class="funcname">test</span>: <span class="event-ids">events 1-2</span></div>
+<table class="locus">
+<tbody class="line-span">
+<tr><td class="linenum"> 27</td> <td class="source">{</td></tr>
+<tr><td class="linenum"/><td class="annotation">^</td></tr>
+<tr><td class="linenum"/><td class="annotation">|</td></tr>
+<tr><td class="linenum"/><td class="annotation">(1) entering 'test'</td></tr>
+<tr><td class="linenum"> 28</td> <td class="source"> register_handler ();</td></tr>
+<tr><td class="linenum"/><td class="annotation"> ~~~~~~~~~~~~~~~~~~~</td></tr>
+<tr><td class="linenum"/><td class="annotation"> |</td></tr>
+<tr><td class="linenum"/><td class="annotation"> (2) calling 'register_handler'</td></tr>
+</tbody>
+</table>
+</td></tr></table>
+<div class="between-ranges-call">
+ <svg height="30" width="150">
+ <defs>
+ <marker id="arrowhead" markerWidth="10" markerHeight="7"
+ refX="0" refY="3.5" orient="auto" stroke="#0088ce" fill="#0088ce">
+ <polygon points="0 0, 10 3.5, 0 7"/>
+ </marker>
+ </defs>
+ <polyline points="20,0 20,10 120,10 120,20"
+ style="fill:none;stroke: #0088ce"
+ marker-end="url(#arrowhead)"/>
+ </svg>
+</div>
+
+<table class="stack-frame-with-margin"><tr>
+ <td class="interprocmargin" style="padding-left: 100px"/>
+ <td class="stack-frame">
+<div class="frame-funcname"><span>register_handler</span></div><table class="event-range-with-margin"><tr>
+ <td class="event-range">
+ <div class="events-hdr"><span class="funcname">register_handler</span>: <span class="event-ids">events 3-4</span></div>
+<table class="locus">
+<tbody class="line-span">
+<tr><td class="linenum"> 22</td> <td class="source">{</td></tr>
+<tr><td class="linenum"/><td class="annotation">^</td></tr>
+<tr><td class="linenum"/><td class="annotation">|</td></tr>
+<tr><td class="linenum"/><td class="annotation">(3) entering 'register_handler'</td></tr>
+<tr><td class="linenum"> 23</td> <td class="source"> signal(SIGINT, int_handler);</td></tr>
+<tr><td class="linenum"/><td class="annotation"> ~~~~~~~~~~~~~~~~~~~~~~~~~~~</td></tr>
+<tr><td class="linenum"/><td class="annotation"> |</td></tr>
+<tr><td class="linenum"/><td class="annotation"> (4) registering 'int_handler' as signal handler</td></tr>
+</tbody>
+</table>
+</td></tr></table>
+</td></tr></table>
+</td></tr></table>
+<div class="between-ranges-return">
+ <svg height="30" width="250">
+ <defs>
+ <marker id="arrowhead" markerWidth="10" markerHeight="7"
+ refX="0" refY="3.5" orient="auto" stroke="#0088ce" fill="#0088ce">
+ <polygon points="0 0, 10 3.5, 0 7"/>
+ </marker>
+ </defs>
+ <polyline points="220,0 220,10 20,10 20,20"
+ style="fill:none;stroke: #0088ce"
+ marker-end="url(#arrowhead)"/>
+ </svg>
+</div>
+
+<table class="event-range-with-margin"><tr>
+ <td class="event-range">
+ <div class="events-hdr"><span class="event-ids">event 5</span></div>
+ (5): later on, when the signal is delivered to the process
+</td></tr></table>
+<div class="between-ranges-call">
+ <svg height="30" width="150">
+ <defs>
+ <marker id="arrowhead" markerWidth="10" markerHeight="7"
+ refX="0" refY="3.5" orient="auto" stroke="#0088ce" fill="#0088ce">
+ <polygon points="0 0, 10 3.5, 0 7"/>
+ </marker>
+ </defs>
+ <polyline points="20,0 20,10 120,10 120,20"
+ style="fill:none;stroke: #0088ce"
+ marker-end="url(#arrowhead)"/>
+ </svg>
+</div>
+
+<table class="stack-frame-with-margin"><tr>
+ <td class="interprocmargin" style="padding-left: 100px"/>
+ <td class="stack-frame">
+<div class="frame-funcname"><span>int_handler</span></div><table class="event-range-with-margin"><tr>
+ <td class="event-range">
+ <div class="events-hdr"><span class="funcname">int_handler</span>: <span class="event-ids">events 6-7</span></div>
+<table class="locus">
+<tbody class="line-span">
+<tr><td class="linenum"> 17</td> <td class="source">{</td></tr>
+<tr><td class="linenum"/><td class="annotation">^</td></tr>
+<tr><td class="linenum"/><td class="annotation">|</td></tr>
+<tr><td class="linenum"/><td class="annotation">(6) entering 'int_handler'</td></tr>
+<tr><td class="linenum"> 18</td> <td class="source"> custom_logger("got signal");</td></tr>
+<tr><td class="linenum"/><td class="annotation"> ~~~~~~~~~~~~~~~~~~~~~~~~~~~</td></tr>
+<tr><td class="linenum"/><td class="annotation"> |</td></tr>
+<tr><td class="linenum"/><td class="annotation"> (7) calling 'custom_logger'</td></tr>
+</tbody>
+</table>
+</td></tr></table>
+<div class="between-ranges-call">
+ <svg height="30" width="150">
+ <defs>
+ <marker id="arrowhead" markerWidth="10" markerHeight="7"
+ refX="0" refY="3.5" orient="auto" stroke="#0088ce" fill="#0088ce">
+ <polygon points="0 0, 10 3.5, 0 7"/>
+ </marker>
+ </defs>
+ <polyline points="20,0 20,10 120,10 120,20"
+ style="fill:none;stroke: #0088ce"
+ marker-end="url(#arrowhead)"/>
+ </svg>
+</div>
+
+<table class="stack-frame-with-margin"><tr>
+ <td class="interprocmargin" style="padding-left: 100px"/>
+ <td class="stack-frame">
+<div class="frame-funcname"><span>custom_logger</span></div><table class="event-range-with-margin"><tr>
+ <td class="event-range">
+ <div class="events-hdr"><span class="funcname">custom_logger</span>: <span class="event-ids">events 8-9</span></div>
+<table class="locus">
+<tbody class="line-span">
+<tr><td class="linenum"> 12</td> <td class="source">{</td></tr>
+<tr><td class="linenum"/><td class="annotation">^</td></tr>
+<tr><td class="linenum"/><td class="annotation">|</td></tr>
+<tr><td class="linenum"/><td class="annotation">(8) entering 'custom_logger'</td></tr>
+<tr><td class="linenum"> 13</td> <td class="source"> fprintf(stderr, "LOG: %s", msg); /* { dg-warning "call to 'fprintf' from within signal handler" } */</td></tr>
+<tr><td class="linenum"/><td class="annotation"> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~</td></tr>
+<tr><td class="linenum"/><td class="annotation"> |</td></tr>
+<tr><td class="linenum"/><td class="annotation"> (9) calling 'fprintf'</td></tr>
+</tbody>
+</table>
+</td></tr></table>
+</td></tr></table>
+</td></tr></table>
+
+</div>
+ """
/* { dg-do compile } */
-/* { dg-options "-O -fdiagnostics-show-caret -fdiagnostics-show-line-numbers" } */
+/* { dg-options "-O -fdiagnostics-show-caret -fdiagnostics-show-line-numbers -fdiagnostics-add-output=experimental-html:javascript=no" } */
/* This is a collection of unittests for diagnostic_show_locus;
see the overview in diagnostic_plugin_test_show_locus.c.
{ dg-end-multiline-output "" } */
#endif
}
+
+/* Use a Python script to verify various properties about the generated
+ HTML file:
+ { dg-final { run-html-pytest diagnostic-test-show-locus-bw-line-numbers.c "diagnostic-test-show-locus.py" } } */
--- /dev/null
+# Verify that diagnostic-show-locus.cc works with HTML output.
+
+from htmltest import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def html_tree():
+ return html_tree_from_env()
+
+#def get_tr_within_thead(thead, idx)
+
+def get_ruler_text(thead, idx):
+ trs = thead.findall('xhtml:tr', ns)
+ tr = trs[idx]
+ tds = tr.findall('xhtml:td', ns)
+ assert len(tds) == 3
+ assert_class(tds[2], 'ruler')
+ return tds[2].text
+
+def test_very_wide_line(html_tree):
+ diag = get_diag_by_index(html_tree, 2)
+ src = get_locus_within_diag(diag)
+
+ # Check ruler
+ thead = src.find('xhtml:thead', ns)
+ assert_class(thead, 'ruler')
+ trs = thead.findall('xhtml:tr', ns)
+ assert len(trs) == 3
+
+ assert get_ruler_text(thead, 0) == ' 0 0 0 0 0 1 1 '
+ assert get_ruler_text(thead, 1) == ' 5 6 7 8 9 0 1 '
+ assert get_ruler_text(thead, 2) == '34567890123456789012345678901234567890123456789012345678901234567890123'
+
+ # Check quoted source
+ tbody = src.find('xhtml:tbody', ns)
+ assert_class(tbody, 'line-span')
+ trs = tbody.findall('xhtml:tr', ns)
+ assert len(trs) == 5
+ assert_quoted_line(trs[0], ' 43', ' float f = foo * bar; /* { dg-warning "95: test" } */')
+ assert_annotation_line(trs[1], ' ~~~~^~~~~')
+ assert_annotation_line(trs[2], ' |')
+ assert_annotation_line(trs[3], ' label 0')
+ assert_annotation_line(trs[4], ' bar * foo')
+
+def test_fixit_insert(html_tree):
+ diag = get_diag_by_index(html_tree, 3)
+ msg = get_message_within_diag(diag)
+ assert msg.text == 'example of insertion hints'
+
+ src = get_locus_within_diag(diag)
+
+ # Check quoted source
+ tbody = src.find('xhtml:tbody', ns)
+ assert_class(tbody, 'line-span')
+ trs = tbody.findall('xhtml:tr', ns)
+ assert len(trs) == 3
+ assert_quoted_line(trs[0], ' 63', ' int a[2][2] = { 0, 1 , 2, 3 }; /* { dg-warning "insertion hints" } */')
+ assert_annotation_line(trs[1], ' ^~~~')
+ assert_annotation_line(trs[2], ' { }')
+
+def test_fixit_remove(html_tree):
+ diag = get_diag_by_index(html_tree, 4)
+ msg = get_message_within_diag(diag)
+ assert msg.text == 'example of a removal hint'
+
+ src = get_locus_within_diag(diag)
+
+ # Check quoted source
+ tbody = src.find('xhtml:tbody', ns)
+ assert_class(tbody, 'line-span')
+ trs = tbody.findall('xhtml:tr', ns)
+ assert len(trs) == 3
+ assert_quoted_line(trs[0], ' 77', ' int a;; /* { dg-warning "example of a removal hint" } */')
+ assert_annotation_line(trs[1], ' ^')
+ assert_annotation_line(trs[2], ' -')
+
+def test_fixit_replace(html_tree):
+ diag = get_diag_by_index(html_tree, 5)
+ msg = get_message_within_diag(diag)
+ assert msg.text == 'example of a replacement hint'
+
+ src = get_locus_within_diag(diag)
+
+ # Check quoted source
+ tbody = src.find('xhtml:tbody', ns)
+ assert_class(tbody, 'line-span')
+ trs = tbody.findall('xhtml:tr', ns)
+ assert len(trs) == 3
+ assert_quoted_line(trs[0], ' 91', ' gtk_widget_showall (dlg); /* { dg-warning "example of a replacement hint" } */')
+ assert_annotation_line(trs[1], ' ^~~~~~~~~~~~~~~~~~')
+ assert_annotation_line(trs[2], ' gtk_widget_show_all')
+
+def test_fixit_insert_newline(html_tree):
+ diag = get_diag_by_index(html_tree, 6)
+ msg = get_message_within_diag(diag)
+ assert msg.text == 'example of newline insertion hint'
+
+ src = get_locus_within_diag(diag)
+
+ # Check quoted source
+ tbody = src.find('xhtml:tbody', ns)
+ assert_class(tbody, 'line-span')
+ trs = tbody.findall('xhtml:tr', ns)
+ assert len(trs) == 4
+ assert_quoted_line(trs[0], ' 109', ' x = a;')
+ assert_annotation_line(trs[1], ' break;',
+ expected_line_num=' +++',
+ expected_left_margin='+')
+ assert_quoted_line(trs[2], ' 110', " case 'b': /* { dg-warning \"newline insertion\" } */")
+ assert_annotation_line(trs[3], ' ^~~~~~~~')
void
test_diagnostic_start_span_fn (const diagnostic_location_print_policy &,
- pretty_printer *pp,
+ to_text &sink,
expanded_location)
{
+ pretty_printer *pp = get_printer (sink);
pp_string (pp, "START_SPAN_FN: ");
pp_newline (pp);
}
html_filename += '.html'
print('html_filename: %r' % html_filename)
return ET.parse(html_filename)
+
+XHTML = 'http://www.w3.org/1999/xhtml'
+ns = {'xhtml': XHTML}
+
+def make_tag(local_name):
+ return f'{{{XHTML}}}' + local_name
+
+def assert_tag(element, expected):
+ assert element.tag == make_tag(expected)
+
+def assert_class(element, expected):
+ assert element.attrib['class'] == expected
+
+def assert_quoted_line(tr, expected_line_num, expected_src):
+ """Verify that tr is a line of quoted source."""
+ tds = tr.findall('xhtml:td', ns)
+ assert len(tds) == 3
+ assert_class(tds[0], 'linenum')
+ assert tds[0].text == expected_line_num
+ assert_class(tds[1], 'left-margin')
+ assert tds[1].text == ' '
+ assert_class(tds[2], 'source')
+ assert tds[2].text == expected_src
+
+def assert_annotation_line(tr, expected_src,
+ expected_line_num=' ',
+ expected_left_margin=' '):
+ """Verify that tr is an annotation line."""
+ tds = tr.findall('xhtml:td', ns)
+ assert len(tds) == 3
+ assert_class(tds[0], 'linenum')
+ assert tds[0].text == expected_line_num
+ assert_class(tds[1], 'left-margin')
+ assert tds[1].text == expected_left_margin
+ assert_class(tds[2], 'annotation')
+ assert tds[2].text == expected_src
+
+def assert_frame(frame, expected_fnname):
+ """
+ Assert that frame is of class 'stack-frame'
+ and has a child showing the expected fnname.
+ """
+ assert_class(frame, 'stack-frame')
+ funcname = frame[0]
+ assert_class(funcname, 'frame-funcname')
+ span = funcname[0]
+ assert_tag(span, 'span')
+ assert span.text == expected_fnname
+
+def assert_event_range_with_margin(element):
+ """
+ Verify that "element" is an event-range-with-margin
+ """
+ assert_tag(element, 'table')
+ assert_class(element, 'event-range-with-margin')
+ tr = element.find('xhtml:tr', ns)
+ assert tr is not None
+ td = tr.find('xhtml:td', ns)
+ assert_class(td, 'event-range')
+
+ events_hdr = td.find('xhtml:div', ns)
+ assert_class(events_hdr, 'events-hdr')
+
+ #...etc
+
+def get_diag_by_index(html_tree, index):
+ root = html_tree.getroot ()
+ assert root.tag == make_tag('html')
+
+ body = root.find('xhtml:body', ns)
+ assert body is not None
+
+ diag_list = body.find('xhtml:div', ns)
+ assert diag_list is not None
+ assert_class(diag_list, 'gcc-diagnostic-list')
+
+ diags = diag_list.findall('xhtml:div', ns)
+ diag = diags[index]
+ assert_class(diag, 'gcc-diagnostic')
+ return diag
+
+def get_message_within_diag(diag_element):
+ msg = diag_element.find('xhtml:span', ns)
+ assert_class(msg, 'gcc-message')
+ return msg
+
+def get_locus_within_diag(diag_element):
+ src = diag_element.find('xhtml:table', ns)
+ assert_class(src, 'locus')
+ return src
--- /dev/null
+/* Classes for creating XML trees by appending.
+ Copyright (C) 2025 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC 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, or (at your option) any later
+version.
+
+GCC 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 GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_XML_PRINTER_H
+#define GCC_XML_PRINTER_H
+
+namespace xml {
+
+class node;
+ class element;
+
+/* A class for creating XML trees by appending to an insertion
+ point, with a stack of open tags. */
+
+class printer
+{
+public:
+ printer (element &insertion_point);
+
+ void push_tag (std::string name,
+ bool preserve_whitespace = false);
+ void push_tag_with_class (std::string name,
+ std::string class_,
+ bool preserve_whitespace = false);
+ void pop_tag ();
+
+ void set_attr (const char *name, std::string value);
+
+ void add_text (std::string text);
+
+ void add_raw (std::string text);
+
+ void push_element (std::unique_ptr<element> new_element);
+
+ void append (std::unique_ptr<node> new_node);
+
+ element *get_insertion_point () const;
+
+private:
+ // borrowed ptrs:
+ std::vector<element *> m_open_tags;
+};
+
+// RAII for push/pop element on xml::printer
+
+class auto_print_element
+{
+public:
+ auto_print_element (printer &printer,
+ std::string name,
+ bool preserve_whitespace = false)
+ : m_printer (printer)
+ {
+ m_printer.push_tag (name, preserve_whitespace);
+ }
+ ~auto_print_element ()
+ {
+ m_printer.pop_tag ();
+ }
+
+private:
+ printer &m_printer;
+};
+
+} // namespace xml
+
+#endif /* GCC_XML_PRINTER_H. */
--- /dev/null
+/* Classes for representing XML trees.
+ Copyright (C) 2024-2025 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC 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, or (at your option) any later
+version.
+
+GCC 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 GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_XML_H
+#define GCC_XML_H
+
+namespace xml {
+
+// Forward decls; indentation reflects inheritance
+struct node;
+ struct text;
+ struct node_with_children;
+ struct document;
+ struct element;
+
+struct node
+{
+ virtual ~node () {}
+ virtual void write_as_xml (pretty_printer *pp,
+ int depth, bool indent) const = 0;
+ virtual text *dyn_cast_text ()
+ {
+ return 0;
+ }
+ void dump (FILE *out) const;
+ void DEBUG_FUNCTION dump () const { dump (stderr); }
+};
+
+struct text : public node
+{
+ text (std::string str)
+ : m_str (std::move (str))
+ {}
+
+ void write_as_xml (pretty_printer *pp,
+ int depth, bool indent) const final override;
+
+ text *dyn_cast_text () final override
+ {
+ return this;
+ }
+
+ std::string m_str;
+};
+
+struct node_with_children : public node
+{
+ void add_child (std::unique_ptr<node> node);
+ void add_text (std::string str);
+
+ std::vector<std::unique_ptr<node>> m_children;
+};
+
+struct document : public node_with_children
+{
+ void write_as_xml (pretty_printer *pp,
+ int depth, bool indent) const final override;
+};
+
+struct element : public node_with_children
+{
+ element (std::string kind, bool preserve_whitespace)
+ : m_kind (std::move (kind)),
+ m_preserve_whitespace (preserve_whitespace)
+ {}
+
+ void write_as_xml (pretty_printer *pp,
+ int depth, bool indent) const final override;
+
+ void set_attr (const char *name, std::string value);
+
+ std::string m_kind;
+ bool m_preserve_whitespace;
+ std::map<std::string, std::string> m_attributes;
+ std::vector<std::string> m_key_insertion_order;
+};
+
+/* A fragment of raw XML source, to be spliced in directly.
+ Use sparingly. */
+
+struct raw : public node
+{
+ raw (std::string xml_src)
+ : m_xml_src (xml_src)
+ {
+ }
+
+ void write_as_xml (pretty_printer *pp,
+ int depth, bool indent) const final override;
+
+ std::string m_xml_src;
+};
+
+} // namespace xml
+
+#endif /* GCC_XML_H. */