From 471556a19e68b372155095c1b11aa568735fe867 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 11 Feb 2016 04:25:00 +0200 Subject: First part of TOC generation support (no links yet) Currently only supported in the HTML output. --- cli/context.cxx | 299 +++++++++++++++++++++++++++++++++++++++++++++++++++-- cli/context.hxx | 67 ++++++++---- cli/generator.cxx | 101 ++++++++++++------ cli/html.cxx | 26 ++--- cli/man.cxx | 6 +- cli/source.cxx | 8 +- cli/txt.cxx | 5 +- tests/toc/toc.cli | 58 +++++++++++ tests/toc/toc.html | 82 +++++++++++++++ 9 files changed, 573 insertions(+), 79 deletions(-) create mode 100644 tests/toc/toc.cli create mode 100644 tests/toc/toc.html diff --git a/cli/context.cxx b/cli/context.cxx index 9ba20ab..ce58779 100644 --- a/cli/context.cxx +++ b/cli/context.cxx @@ -98,12 +98,14 @@ namespace context:: context (ostream& os_, + output_type ot_, semantics::cli_unit& unit_, options_type const& ops) : data_ (new (shared) data), os (os_), unit (unit_), options (ops), + ot (ot_), modifier (options.generate_modifier ()), specifier (options.generate_specifier ()), inl (data_->inl_), @@ -112,7 +114,9 @@ context (ostream& os_, cli (data_->cli_), reserved_name_map (options.reserved_name ()), keyword_set (data_->keyword_set_), - link_regex (data_->link_regex_) + link_regex (data_->link_regex_), + toc (data_->toc_), + tocs (data_->tocs_) { if (options.suppress_usage ()) usage = ut_none; @@ -150,6 +154,8 @@ context (ostream& os_, throw generation_failed (); } } + + toc = 0; } context:: @@ -158,6 +164,7 @@ context (context& c) os (c.os), unit (c.unit), options (c.options), + ot (c.ot), modifier (c.modifier), specifier (c.specifier), usage (c.usage), @@ -167,7 +174,9 @@ context (context& c) cli (c.cli), reserved_name_map (c.reserved_name_map), keyword_set (c.keyword_set), - link_regex (c.link_regex) + link_regex (c.link_regex), + toc (c.toc), + tocs (c.tocs) { } @@ -1120,7 +1129,7 @@ html_margin (string& v) string context:: -format (semantics::scope& scope, output_type ot, string const& s, bool para) +format (semantics::scope& scope, string const& s, bool para) { stack blocks; blocks.push (block (block::text, para)); // Top-level. @@ -1165,15 +1174,61 @@ format (semantics::scope& scope, output_type ot, string const& s, bool para) l = s.c_str () + b; n = (last ? s.size () : e) - b; - // Perform variable expansions (\$var$). + // Perform variable expansions (\$var$). Also detect the switch + // to/from TOC mode. // + unsigned short t (toc); if (substitute (scope, l, n, subst)) { + if (subst.empty ()) + continue; + + if (t != toc) + { + // This is a TOC prologue/epilogue (returned by start/end_toc()) and + // we shouldn't be formatting it. + // + assert (blocks.size () == 1); + string& v (blocks.top ().value); + + // Require that \$TOC$ appears in its own doc string. Failed this + // it is tricky to get indentation right. + // + if (!v.empty () || !last) + { + cerr << "error: TOC variable should be in its own documentation " + << "string" << endl; + throw generation_failed (); + } + + switch (ot) + { + case ot_plain: break; + case ot_html: + { + // Different "newline protocol" inside TOC. + // + v += subst; + + if (toc) + v += '\n'; + } + case ot_man: break; + } + + continue; + } + l = subst.c_str (); n = subst.size (); } } + // If this is TOC phase 2, then we simply ignore everything. + // + if (toc == 2) + continue; + const char* ol (l); // Original, full line for diagnostics. size_t on (n); @@ -1412,8 +1467,13 @@ format (semantics::scope& scope, output_type ot, string const& s, bool para) case block::pre: break; // No push. } - // Output paragraph text. + // Output paragraph text. If we are in TOC mode and this is a top-level + // block, then pretend we don't have anything to write. For non-top-level + // blocks, we handle this below, when we pop them. // + if (toc && blocks.size () == 1) + n = 0; + if (n != 0) { block& b (blocks.top ()); @@ -1557,6 +1617,135 @@ format (semantics::scope& scope, output_type ot, string const& s, bool para) block& b (blocks.top ()); string& v (b.value); + // Handle poping into top-level block in TOC mode. + // + if (toc && blocks.size () == 1) + { + if (pb.kind == block::h) + { + switch (ot) + { + case ot_plain: break; + case ot_html: + { + char t (ph[0]); + + // Unwind heading levels that are deeper ("more sub") than us. + // + for (; tocs.size () != 1; tocs.pop_back ()) + { + toc_entry const& e (tocs.back ()); + + bool pop (true); + + switch (t) + { + case '0': + case 'H': + case '1': break; // Always unwind. + case 'h': + { + pop = html_h[1] == '1' || e.type == 'h' || e.type == '2'; + break; + } + case '2': pop = e.type == '2'; break; + } + + if (!pop) + break; + + // If we have sub-headings, then we need to close the table. + // + if (e.count != 0) + { + size_t l (tocs.size ()); + + v += string (l * 4 - 2, ' ') + "\n"; + v += string (l * 4 - 4, ' ') + "\n"; + } + else + // Otherwise it is inline. + // + v += "\n"; + } + + size_t l (tocs.size ()); + toc_entry& e (tocs.back ()); + + // If this is a first sub-entry, then we need to open the + // sub-table. + // + string in ((l * 4) - 2, ' '); + + if (l > 1 && e.count == 0) + { + v += "\n"; + v += in + "\n"; + } + + in += " "; + + switch (t) + { + case 'H': + { + v += in + "\n"; + break; + } + case '0': + case '1': + case 'h': + case '2': + { + // Calculate Chapter(X)/Section(X.Y)/Subsection(X.Y.Z) unless + // it is the preface (or a subsection thereof). + // + string n; + if (t != '0') + { + ++e.count; + + for (toc_stack::const_iterator i (tocs.begin ()); + i != tocs.end (); + ++i) + { + if (i->type == '0') + { + n.clear (); + break; + } + + if (!n.empty ()) + n += '.'; + + ostringstream os; + os << i->count; + n += os.str (); + } + } + + v += in + "\n"; + } + else + // Otherwise it is inline. + // + v += "\n"; + } + + v += "
" + pv + "
" + n + "" + pv; // No newline + tocs.push_back (toc_entry (t)); + break; + } + } + + // Same as in non-TOC mode below. + // + // @@ This only works for a single string fragment. + // + if (ph[0] == '0' || ph[0] == '1') + html_h = "h2"; + + break; + } + case ot_man: break; + } + } + + continue; + } + switch (ot) { case ot_plain: @@ -1752,7 +1941,61 @@ format (semantics::scope& scope, output_type ot, string const& s, bool para) } string context:: -substitute (const string& s, semantics::cli_unit& u, const path* d) +start_toc () +{ + switch (ot) + { + case ot_plain: break; + case ot_html: + { + tocs.push_back (toc_entry ('\0')); + return " "; + } + case ot_man: break; + } +} + +string context:: +end_toc () +{ + switch (ot) + { + case ot_plain: break; + case ot_html: + { + string v; + + // Unwind the TOC stack until we reach top level. Same code as in + // format(). + // + for (; tocs.size () != 1; tocs.pop_back ()) + { + toc_entry const& e (tocs.back ()); + + // If we have sub-headings, then we need to close the table. + // + if (e.count != 0) + { + size_t l (tocs.size ()); + + v += string (l * 4 - 2, ' ') + "
\n"; + v += string (l * 4 - 4, ' ') + "
"; + return v; + } + case ot_man: break; + } +} + +string context:: +substitute (const string& s, const path* d) { string r; @@ -1832,9 +2075,31 @@ substitute (const string& s, semantics::cli_unit& u, const path* d) } else { + // Handle special variables. + // + if (v == "TOC") + { + if (!r.empty () || p + 1 != n) + { + cerr << "error: TOC variable should be on its own line" << endl; + throw generation_failed (); + } + + // Invert the TOC mode. + // + if ((toc = toc ? 0 : 1)) + r = start_toc (); + else + r = end_toc (); + + return r; + } + // Lookup and substiute the variable. // - if (semantics::doc* d = u.lookup ("", "var: " + v)) + using semantics::doc; + + if (doc* d = unit.lookup ("", "var: " + v)) r += d->front (); else { @@ -1900,6 +2165,26 @@ substitute (semantics::scope& scope, const char* s, size_t n, string& result) ++e; string v (s, e, p - e); + // Handle special variables. + // + if (v == "TOC") + { + if (!result.empty () || p + 1 != n) + { + cerr << "error: TOC variable should be its own paragraph" << endl; + throw generation_failed (); + } + + // Invert the TOC mode. + // + if ((toc = toc ? 0 : 1)) + result = start_toc (); + else + result = end_toc (); + + return true; + } + // Lookup and substiute. // using semantics::doc; diff --git a/cli/context.hxx b/cli/context.hxx index 9f10a00..79c9aab 100644 --- a/cli/context.hxx +++ b/cli/context.hxx @@ -71,6 +71,17 @@ public: semantics::cli_unit& unit; options_type const& options; + // Documentation output type. + // + enum output_type + { + ot_plain, + ot_html, + ot_man + }; + + output_type ot; + bool modifier; bool specifier; usage_type usage; @@ -88,14 +99,35 @@ public: regex_mapping const& link_regex; + // TOC phase. + // + // 0 - non-TOC + // 1 - generating TOC after seeing expansion but before restart + // 2 - generating TOC after restart + // 0 - non-TOC after seeing expansion after restart + // + unsigned short& toc; + + struct toc_entry + { + toc_entry (char t = '\0', size_t c = 0): type (t), count (c) {} + + char type; // Entry type (output type-specific, usually X from \hX). + size_t count; // Number of sub-entries so far. + }; + + typedef std::vector toc_stack; + toc_stack& tocs; + private: struct data { string inl_; string cli_; keyword_set_type keyword_set_; - regex_mapping link_regex_; + unsigned short toc_; + toc_stack tocs_; }; public: @@ -111,13 +143,6 @@ public: // the -style constructs to \i{arg}. Format converts the string // to the output format. // - enum output_type - { - ot_plain, - ot_html, - ot_man - }; - static string translate_arg (string const&, std::set&); @@ -127,24 +152,27 @@ public: // If para is true, start a new paragraph. // string - format (semantics::scope&, output_type, string const&, bool para); + format (semantics::scope&, string const&, bool para); void format_line (output_type, string&, const char*, size_t); + // Called when we switch to the TOC mode and when we exit it, + // respectively. + // + string + start_toc (); + + string + end_toc (); + // Substitute doc variable expansions ($var$). Var must be a C identifier. // If the path is not NULL, then also recognize names that start with either // ./ or ../ and treat them as files relative to path. Such file expansions - // are substituted with the files' contents. + // are substituted with the file contents. // - static string - substitute (const string&, semantics::cli_unit&, const path* = 0); - string - substitute (const string& s, const path* p = 0) - { - return substitute (s, unit, p); - } + substitute (const string&, const path* = 0); // Substitute doc variable expansions (\$var$). Note that it leaves escapes // (\\$) as is. Return true if any substitutions have been made, in which @@ -204,7 +232,10 @@ public: first_sentence (string const&); public: - context (std::ostream&, semantics::cli_unit&, options_type const&); + context (std::ostream&, + output_type, + semantics::cli_unit&, + options_type const&); context (context&); diff --git a/cli/generator.cxx b/cli/generator.cxx index 5964c25..cbb3ec7 100644 --- a/cli/generator.cxx +++ b/cli/generator.cxx @@ -80,14 +80,32 @@ namespace } void - append (ostream& os, - vector const& text, - string const& file, - semantics::cli_unit& u) + append (context& ctx, string const& s, const path* d = 0) + { + // Detect the switch to/from TOC mode. + // + unsigned short t (ctx.toc); + string const& r (ctx.substitute (s, d)); + + if (t != ctx.toc) + { + if (!r.empty ()) // TOC prologue/epilogue (returned by start/end_toc()). + ctx.os << r << endl; + } + // Skip it if we are in the TOC mode. + // + else if (!t) + ctx.os << r << endl; + } + + void + append (context& ctx, vector const& text, string const& file) { for (vector::const_iterator i (text.begin ()); i != text.end (); ++i) - os << context::substitute (*i, u) << endl; + { + append (ctx, *i); + } if (!file.empty ()) { @@ -101,7 +119,7 @@ namespace // the delimiter. // for (string s; getline (ifs, s); ) - os << context::substitute (s, u, &d) << endl; + append (ctx, s, &d); } } } @@ -176,7 +194,7 @@ generate (options const& ops, semantics::cli_unit& unit, path const& p) // Process names. // { - context ctx (cerr, unit, ops); + context ctx (cerr, context::ot_plain, unit, ops); process_names (ctx); } @@ -265,7 +283,7 @@ generate (options const& ops, semantics::cli_unit& unit, path const& p) // HXX // { - context ctx (hxx, unit, ops); + context ctx (hxx, context::ot_plain, unit, ops); string guard (make_guard (gp + hxx_name, ctx)); @@ -277,7 +295,7 @@ generate (options const& ops, semantics::cli_unit& unit, path const& p) // hxx << "// Begin prologue." << endl << "//" << endl; - append (hxx, ops.hxx_prologue (), ops.hxx_prologue_file (), unit); + append (ctx, ops.hxx_prologue (), ops.hxx_prologue_file ()); hxx << "//" << endl << "// End prologue." << endl << endl; @@ -304,7 +322,7 @@ generate (options const& ops, semantics::cli_unit& unit, path const& p) // hxx << "// Begin epilogue." << endl << "//" << endl; - append (hxx, ops.hxx_epilogue (), ops.hxx_epilogue_file (), unit); + append (ctx, ops.hxx_epilogue (), ops.hxx_epilogue_file ()); hxx << "//" << endl << "// End epilogue." << endl << endl; @@ -316,13 +334,13 @@ generate (options const& ops, semantics::cli_unit& unit, path const& p) // if (inl) { - context ctx (ixx, unit, ops); + context ctx (ixx, context::ot_plain, unit, ops); // Copy prologue. // ixx << "// Begin prologue." << endl << "//" << endl; - append (ixx, ops.ixx_prologue (), ops.ixx_prologue_file (), unit); + append (ctx, ops.ixx_prologue (), ops.ixx_prologue_file ()); ixx << "//" << endl << "// End prologue." << endl << endl; @@ -342,7 +360,7 @@ generate (options const& ops, semantics::cli_unit& unit, path const& p) // ixx << "// Begin epilogue." << endl << "//" << endl; - append (ixx, ops.ixx_epilogue (), ops.ixx_epilogue_file (), unit); + append (ctx, ops.ixx_epilogue (), ops.ixx_epilogue_file ()); ixx << "//" << endl << "// End epilogue." << endl; } @@ -350,13 +368,13 @@ generate (options const& ops, semantics::cli_unit& unit, path const& p) // CXX // { - context ctx (cxx, unit, ops); + context ctx (cxx, context::ot_plain, unit, ops); // Copy prologue. // cxx << "// Begin prologue." << endl << "//" << endl; - append (cxx, ops.cxx_prologue (), ops.cxx_prologue_file (), unit); + append (ctx, ops.cxx_prologue (), ops.cxx_prologue_file ()); cxx << "//" << endl << "// End prologue." << endl << endl; @@ -385,7 +403,7 @@ generate (options const& ops, semantics::cli_unit& unit, path const& p) // cxx << "// Begin epilogue." << endl << "//" << endl; - append (cxx, ops.cxx_epilogue (), ops.cxx_epilogue_file (), unit); + append (ctx, ops.cxx_epilogue (), ops.cxx_epilogue_file ()); cxx << "//" << endl << "// End epilogue." << endl << endl; @@ -420,13 +438,20 @@ generate (options const& ops, semantics::cli_unit& unit, path const& p) // The explicit cast helps VC++ 8.0 overcome its issues. // ostream& os (ops.stdout_ () ? cout : static_cast (man)); + context ctx (os, context::ot_man, unit, ops); - append (os, ops.man_prologue (), ops.man_prologue_file (), unit); - - context ctx (os, unit, ops); - generate_man (ctx); + for (bool first (true); first || ctx.toc; first = false) + { + append (ctx, ops.man_prologue (), ops.man_prologue_file ()); + generate_man (ctx); + append (ctx, ops.man_epilogue (), ops.man_epilogue_file ()); - append (os, ops.man_epilogue (), ops.man_epilogue_file (), unit); + if (ctx.toc) + { + assert (first); // Second run should end in non-TOC mode. + ctx.toc++; // TOC phase after restart. + } + } } // HTML output @@ -460,13 +485,20 @@ generate (options const& ops, semantics::cli_unit& unit, path const& p) // The explicit cast helps VC++ 8.0 overcome its issues. // ostream& os (ops.stdout_ () ? cout : static_cast (html)); + context ctx (os, context::ot_html, unit, ops); - append (os, ops.html_prologue (), ops.html_prologue_file (), unit); - - context ctx (os, unit, ops); - generate_html (ctx); + for (bool first (true); first || ctx.toc; first = false) + { + append (ctx, ops.html_prologue (), ops.html_prologue_file ()); + generate_html (ctx); + append (ctx, ops.html_epilogue (), ops.html_epilogue_file ()); - append (os, ops.html_epilogue (), ops.html_epilogue_file (), unit); + if (ctx.toc) + { + assert (first); // Second run should end in non-TOC mode. + ctx.toc++; // TOC phase after restart. + } + } } // txt output @@ -497,13 +529,20 @@ generate (options const& ops, semantics::cli_unit& unit, path const& p) // The explicit cast helps VC++ 8.0 overcome its issues. // ostream& os (ops.stdout_ () ? cout : static_cast (txt)); + context ctx (os, context::ot_plain, unit, ops); - append (os, ops.txt_prologue (), ops.txt_prologue_file (), unit); - - context ctx (os, unit, ops); - generate_txt (ctx); + for (bool first (true); first || ctx.toc; first = false) + { + append (ctx, ops.txt_prologue (), ops.txt_prologue_file ()); + generate_txt (ctx); + append (ctx, ops.txt_epilogue (), ops.txt_epilogue_file ()); - append (os, ops.txt_epilogue (), ops.txt_epilogue_file (), unit); + if (ctx.toc) + { + assert (first); // Second run should end in non-TOC mode. + ctx.toc++; // TOC phase after restart. + } + } } auto_rm.cancel (); diff --git a/cli/html.cxx b/cli/html.cxx index da94a65..2d018e0 100644 --- a/cli/html.cxx +++ b/cli/html.cxx @@ -139,10 +139,10 @@ namespace if (n > 1) translate_arg (ds[0], arg_set); - string s (format (ds.scope (), - ot_html, - escape_html (translate (d, arg_set)), - true)); + unsigned short t (toc); // Detect the switch to/from TOC mode. + + string s ( + format (ds.scope (), escape_html (translate (d, arg_set)), true)); if (s.empty ()) return; @@ -154,9 +154,11 @@ namespace list_ = false; } - wrap_lines (os, s, 2); - os << endl - << endl; + wrap_lines (os, s, (t || toc) ? 0 : 2); // TOC mode does its own thing. + + if (!toc) // TOC mode does its own thing. + os << endl + << endl; } private: @@ -179,6 +181,9 @@ namespace if (options.suppress_undocumented () && doc.empty ()) return; + if (toc) + return; // No option documentation in the TOC mode. + if (!list_) { os << "
" << endl; @@ -211,7 +216,7 @@ namespace translate_arg ( doc.size () > 0 ? doc[0] : string (""), arg_set)); - os << ' ' << format (o.scope (), ot_html, escape_html (s), false); + os << ' ' << format (o.scope (), escape_html (s), false); } os << "" << endl; @@ -234,10 +239,7 @@ namespace // Format the documentation string. // - d = format (o.scope (), - ot_html, - escape_html (translate (d, arg_set)), - false); + d = format (o.scope (), escape_html (translate (d, arg_set)), false); wrap_lines (os, "
" + d + "
", 4); os << endl; diff --git a/cli/man.cxx b/cli/man.cxx index 7e2757c..e55bc17 100644 --- a/cli/man.cxx +++ b/cli/man.cxx @@ -94,7 +94,7 @@ namespace if (n > 1) translate_arg (ds[0], arg_set); - string s (format (ds.scope (), ot_man, translate (d, arg_set), true)); + string s (format (ds.scope (), translate (d, arg_set), true)); if (s.empty ()) return; @@ -145,7 +145,7 @@ namespace translate_arg ( doc.size () > 0 ? doc[0] : string (""), arg_set)); - os << ' ' << format (o.scope (), ot_man, s, false); + os << ' ' << format (o.scope (), s, false); } os << "\"" << endl; @@ -168,7 +168,7 @@ namespace // Format the documentation string. // - d = format (o.scope (), ot_man, translate (d, arg_set), false); + d = format (o.scope (), translate (d, arg_set), false); wrap_lines (os, d); os << endl; diff --git a/cli/source.cxx b/cli/source.cxx index 14a6a9a..57f5922 100644 --- a/cli/source.cxx +++ b/cli/source.cxx @@ -260,7 +260,7 @@ namespace if (n > 1 && options.ansi_color ()) translate_arg (ds[0], arg_set); - d = format (ds.scope (), ot_plain, translate (d, arg_set), true); + d = format (ds.scope (), translate (d, arg_set), true); if (d.empty ()) return; @@ -331,7 +331,7 @@ namespace s = translate_arg (s, arg_set); } - l += txt_size (format (o.scope (), ot_plain, s, false)); + l += txt_size (format (o.scope (), s, false)); } if (l > length_) @@ -422,7 +422,7 @@ namespace if (color) s = translate_arg (s, arg_set); - s = format (o.scope (), ot_plain, s, false); + s = format (o.scope (), s, false); os << escape_str (s); l += txt_size (s); @@ -458,7 +458,7 @@ namespace if (color) d = translate (d, arg_set); - d = format (o.scope (), ot_plain, d, false); + d = format (o.scope (), d, false); if (!d.empty ()) wrap_lines (os, d, length_ + 1, l); // +1 for extra space after arg. diff --git a/cli/txt.cxx b/cli/txt.cxx index ccce8ef..388ae0f 100644 --- a/cli/txt.cxx +++ b/cli/txt.cxx @@ -156,10 +156,7 @@ namespace if (n > 1 && options.ansi_color ()) translate_arg (ds[0], arg_set); - string s (format (ds.scope (), - ot_plain, - translate (d, arg_set), - true)); + string s (format (ds.scope (), translate (d, arg_set), true)); if (s.empty ()) return; diff --git a/tests/toc/toc.cli b/tests/toc/toc.cli new file mode 100644 index 0000000..5e9c43a --- /dev/null +++ b/tests/toc/toc.cli @@ -0,0 +1,58 @@ +"\h1|Table of Contents|" +"\$TOC$" + +" +\h0|Preface| + +This document describes something awesome. + +\h|About This Document| + +And this document is also awesome. + +\h|More Information| + +It is so awesome that no further information will be required." + +" +\H|PART I| + +Start of part one. + +\h1|Introduction| + +Beginning of the first chapter. + +\h|Architecture and Workflow| + +Some basics. + +\h|Benefits| + +You will like them. + +\h1|Hello World| + +Beginning of the second chapter. + +\h|Setup| + +More basics. + +\h|Compiling| + +How to build the example + +\h2|Compiling with GCC| + +GCC + +\h2|Compiling with Clang| + +Clang + +\h|Conclusion| + +Some remarks. + +" diff --git a/tests/toc/toc.html b/tests/toc/toc.html new file mode 100644 index 0000000..383ff12 --- /dev/null +++ b/tests/toc/toc.html @@ -0,0 +1,82 @@ +

Table of Contents

+ + + + + + +
Preface + + + +
About This Document
More Information
+
PART I
1Introduction + + + +
1.1Architecture and Workflow
1.2Benefits
+
2Hello World + + + + +
2.1Setup
2.2Compiling + + + +
2.2.1Compiling with GCC
2.2.2Compiling with Clang
+
2.3Conclusion
+
+ +

Preface

+ +

This document describes something awesome.

+ +

About This Document

+ +

And this document is also awesome.

+ +

More Information

+ +

It is so awesome that no further information will be required.

+ +

PART I

+ +

Start of part one.

+ +

Introduction

+ +

Beginning of the first chapter.

+ +

Architecture and Workflow

+ +

Some basics.

+ +

Benefits

+ +

You will like them.

+ +

Hello World

+ +

Beginning of the second chapter.

+ +

Setup

+ +

More basics.

+ +

Compiling

+ +

How to build the example

+ +

Compiling with GCC

+ +

GCC

+ +

Compiling with Clang

+ +

Clang

+ +

Conclusion

+ +

Some remarks.

+ -- cgit v1.1