diff options
author | Karen Arutyunov <karen@codesynthesis.com> | 2020-04-08 14:51:57 +0300 |
---|---|---|
committer | Karen Arutyunov <karen@codesynthesis.com> | 2020-04-27 11:38:53 +0300 |
commit | 720c5a33b6a49cf328fdd7611f49153cf8f60247 (patch) | |
tree | 9725f3d1f42ec90fde84520f49647edea013ce5e /cli/cli | |
parent | 3183f3bb927a90783ae0aeaf190a0919377aabe4 (diff) |
Separate tests and examples into individual packages
Also make cli module to be explicitly enabled via the config.cli configuration
variable.
Diffstat (limited to 'cli/cli')
74 files changed, 21812 insertions, 0 deletions
diff --git a/cli/cli/.gitignore b/cli/cli/.gitignore new file mode 100644 index 0000000..903d015 --- /dev/null +++ b/cli/cli/.gitignore @@ -0,0 +1,7 @@ +cli +version.hxx + +# Unit test executables and Testscript output directories (can be symlinks). +# +*.test +test-*.test diff --git a/cli/cli/buildfile b/cli/cli/buildfile new file mode 100644 index 0000000..ff562c2 --- /dev/null +++ b/cli/cli/buildfile @@ -0,0 +1,75 @@ +# file : cli/buildfile +# license : MIT; see accompanying LICENSE file + +import libs = libcutl%lib{cutl} + +./: exe{cli}: cxx{cli} libue{cli} + +# Target metadata, see also --build2-metadata in cli.cxx. +# +exe{cli}: +{ + cli.version = $version.project_id + cli.checksum = $version +} + +libue{cli}: {hxx ixx txx cxx}{** -cli -version -options -**.test...} \ + {hxx}{version} {hxx ixx cxx}{options} \ + $libs + +hxx{version}: in{version} $src_root/manifest + +# Unit tests. +# +exe{*.test}: +{ + test = true + install = false +} + +for t: cxx{**.test...} +{ + d = $directory($t) + n = $name($t)... + + ./: $d/exe{$n}: $t $d/{hxx ixx txx}{+$n} $d/testscript{+$n} + $d/exe{$n}: libue{cli}: bin.whole = false +} + +# Build options. +# +# Pass the copyright notice extracted from the LICENSE file. +# +copyright = $process.run_regex(cat $src_root/LICENSE, \ + 'Copyright \(c\) (.+)\.', \ + '\1') + +obj{cli}: cxx.poptions += -DCLI_COPYRIGHT=\"$copyright\" + +# Generated options parsing code. +# +# @@ This will eventually be replaced with an ah hoc recipe. +# +if ($config.cli != [null] && $config.cli != false) +{ + cli.cxx{options}: cli{options} + + cli.options += --include-with-brackets --include-prefix cli \ +--guard-prefix CLI --generate-file-scanner --generate-specifier \ +--generate-modifier --reserved-name stdout + + cli.cxx{*}: + { + # Include the generated cli files into the distribution and don't remove + # them when cleaning in src (so that clean results in a state identical to + # distributed). + # + dist = true + clean = ($src_root != $out_root) + + # We keep the generated code in the repository so copy it back to src + # in case of a forwarded configuration. + # + backlink = overwrite + } +} diff --git a/cli/cli/cli.cxx b/cli/cli/cli.cxx new file mode 100644 index 0000000..c57caee --- /dev/null +++ b/cli/cli/cli.cxx @@ -0,0 +1,165 @@ +// file : cli/cli.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <vector> +#include <string> +#include <memory> // unique_ptr +#include <fstream> +#include <iostream> + +#include <cutl/compiler/code-stream.hxx> + +#include <cli/semantics/doc.hxx> + +#include <cli/options.hxx> +#include <cli/parser.hxx> +#include <cli/generator.hxx> + +#include <cli/version.hxx> + +using namespace std; +using namespace cutl; + +int +main (int argc, char* argv[]) +{ + ostream& e (cerr); + const char* file (0); + + try + { + cli::argv_file_scanner scan (argc, argv, "--options-file"); + options ops (scan); + + // Handle --build2-metadata (see also buildfile). + // + if (ops.build2_metadata ()) + { + ostream& o (cout); + + o << "# build2 buildfile cli" << endl + << "cli.version = '" << CLI_VERSION_ID << '\'' << endl + << "cli.checksum = '" << CLI_CHECKSUM << '\'' << endl; + + return 0; + } + + // Handle --version + // + if (ops.version ()) + { + ostream& o (cout); + + o << "CLI (command line interface compiler) " << CLI_VERSION_ID << endl + << "Copyright (c) " << CLI_COPYRIGHT << "." << endl; + + o << "This is free software; see the source for copying conditions. " + << "There is NO\nwarranty; not even for MERCHANTABILITY or FITNESS " + << "FOR A PARTICULAR PURPOSE." << endl; + + return 0; + } + + // Handle --help + // + if (ops.help ()) + { + ostream& o (cout); + + o << "Usage: " << argv[0] << " [options] file" << endl + << "Options:" << endl; + + options::print_usage (o); + + return 0; + } + + if (!scan.more ()) + { + e << "error: no input file specified" << endl + << "info: try '" << argv[0] << " --help' for more information" << endl; + + return 1; + } + + // Extract include search paths. + // + parser::paths include_paths; + for (vector<string>::const_iterator i (ops.include_path ().begin ()); + i != ops.include_path ().end (); ++i) + { + // Invalid path exception is handled below. + // + include_paths.push_back (semantics::path (*i)); + } + + // Open the input file. + // + file = scan.next (); + semantics::path path (file); + + ifstream ifs (path.string ().c_str ()); + if (!ifs.is_open ()) + { + e << path << ": error: unable to open in read mode" << endl; + return 1; + } + + ifs.exceptions (ifstream::failbit | ifstream::badbit); + + // Parse and generate. + // + parser p (include_paths); + unique_ptr<semantics::cli_unit> unit (p.parse (ifs, path)); + + // Merge documentation variables from the command line. + // + for (map<string, string>::const_iterator i (ops.docvar ().begin ()); + i != ops.docvar ().end (); + ++i) + { + using semantics::doc; + + // Values specified in the .cli file override command line. + // + if (unit->lookup<doc> ("", "var: " + i->first) != 0) + continue; + + doc& d (unit->new_node<doc> (semantics::path ("<command line>"), 0, 0)); + unit->new_edge<semantics::names> (*unit, d, "var: " + i->first); + d.push_back (i->second); + } + + generator g; + g.generate (ops, *unit, path); + } + catch (cli::exception const& ex) + { + e << ex << endl; + return 1; + } + catch (semantics::invalid_path const& ex) + { + e << "error: '" << ex.path () << "' is not a valid filesystem path" + << endl; + return 1; + } + catch (std::ios_base::failure const&) + { + e << file << ": error: read failure" << endl; + return 1; + } + catch (parser::invalid_input const&) + { + // Diagnostics has already been issued by the parser. + // + return 1; + } + catch (generator::failed const&) + { + // Diagnostics has already been issued by the generator. + // + return 1; + } +} diff --git a/cli/cli/context.cxx b/cli/cli/context.cxx new file mode 100644 index 0000000..54bb988 --- /dev/null +++ b/cli/cli/context.cxx @@ -0,0 +1,2742 @@ +// file : cli/context.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <stack> +#include <vector> +#include <cstring> // strncmp() +#include <fstream> +#include <sstream> +#include <iostream> + +#include <cli/context.hxx> + +using namespace std; + +namespace +{ + char const* keywords[] = + { + "NULL", + "and", + "asm", + "auto", + "bitand", + "bitor", + "bool", + "break", + "case", + "catch", + "char", + "class", + "compl", + "const", + "const_cast", + "continue", + "default", + "delete", + "do", + "double", + "dynamic_cast", + "else", + "end_eq", + "enum", + "explicit", + "export", + "extern", + "false", + "float", + "for", + "friend", + "goto", + "if", + "inline", + "int", + "long", + "mutable", + "namespace", + "new", + "not", + "not_eq", + "operator", + "or", + "or_eq", + "private", + "protected", + "public", + "register", + "reinterpret_cast", + "return", + "short", + "signed", + "sizeof", + "static", + "static_cast", + "struct", + "switch", + "template", + "this", + "throw", + "true", + "try", + "typedef", + "typeid", + "typename", + "union", + "unsigned", + "using", + "virtual", + "void", + "volatile", + "wchar_t", + "while", + "xor", + "xor_eq" + }; +} + +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_), + gen_modifier (options.generate_modifier ()), + gen_specifier (options.generate_specifier () || + options.generate_merge ()), + gen_parse (options.generate_parse ()), + gen_merge (options.generate_merge ()), + inl (data_->inl_), + opt_prefix (options.option_prefix ()), + opt_sep (options.option_separator ()), + cli (data_->cli_), + reserved_name_map (options.reserved_name ()), + keyword_set (data_->keyword_set_), + link_regex (data_->link_regex_), + id_set (data_->id_set_), + ref_set (data_->ref_set_), + heading_map (data_->heading_map_), + toc (data_->toc_), + tocs (data_->tocs_) +{ + if (options.suppress_usage ()) + gen_usage = ut_none; + else + { + if (options.long_usage ()) + gen_usage = options.short_usage () ? ut_both : ut_long; + else + gen_usage = ut_short; + } + + if (!options.suppress_inline ()) + data_->inl_ = "inline "; + + data_->cli_ = options.cli_namespace (); + + if (!cli.empty () && cli[0] != ':') + data_->cli_ = "::" + data_->cli_; + + for (size_t i (0); i < sizeof (keywords) / sizeof (char*); ++i) + data_->keyword_set_.insert (keywords[i]); + + // Link regex. + // + for (vector<string>::const_iterator i (ops.link_regex ().begin ()); + i != ops.link_regex ().end (); ++i) + { + try + { + data_->link_regex_.push_back (regexsub (*i)); + } + catch (const regex_format& e) + { + cerr << "error: invalid regex '" << *i << "': " << e.what () << endl; + throw generation_failed (); + } + } + + toc = 0; +} + +context:: +context (context& c) + : data_ (c.data_), + os (c.os), + unit (c.unit), + options (c.options), + ot (c.ot), + gen_modifier (c.gen_modifier), + gen_specifier (c.gen_specifier), + gen_parse (c.gen_parse), + gen_merge (c.gen_merge), + gen_usage (c.gen_usage), + inl (c.inl), + opt_prefix (c.opt_prefix), + opt_sep (c.opt_sep), + cli (c.cli), + reserved_name_map (c.reserved_name_map), + keyword_set (c.keyword_set), + link_regex (c.link_regex), + id_set (c.id_set), + ref_set (c.ref_set), + heading_map (c.heading_map), + toc (c.toc), + tocs (c.tocs) +{ +} + +string context:: +escape (string const& name) const +{ + typedef string::size_type size; + + string r; + size n (name.size ()); + + // In most common cases we will have that many characters. + // + r.reserve (n); + + for (size i (0); i < n; ++i) + { + char c (name[i]); + + if (i == 0) + { + if (!((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + c == '_')) + r = (c >= '0' && c <= '9') ? "cxx_" : "cxx"; + } + + if (!((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '_')) + r += '_'; + else + r += c; + } + + if (r.empty ()) + r = "cxx"; + + // Custom reserved words. + // + reserved_name_map_type::const_iterator i (reserved_name_map.find (r)); + + if (i != reserved_name_map.end ()) + { + if (!i->second.empty ()) + return i->second; + else + r += L'_'; + } + + // Keywords + // + if (keyword_set.find (r) != keyword_set.end ()) + { + r += '_'; + + // Re-run custom words. + // + i = reserved_name_map.find (r); + + if (i != reserved_name_map.end ()) + { + if (!i->second.empty ()) + return i->second; + else + r += L'_'; + } + } + + return r; +} + +string context:: +process_link_target (const string& tg) +{ + bool t (options.link_regex_trace ()); + + if (t) + cerr << "link: '" << tg << "'" << endl; + + string r; + bool found (false); + + for (regex_mapping::const_iterator i (link_regex.begin ()); + i != link_regex.end (); ++i) + { + if (t) + cerr << "try: '" << i->regex () << "' : "; + + if (i->match (tg)) + { + r = i->replace (tg); + found = true; + + if (t) + cerr << "'" << r << "' : "; + } + + if (t) + cerr << (found ? '+' : '-') << endl; + + if (found) + break; + } + + return found ? r : tg; +} + + +string context:: +translate_arg (string const& s, std::set<string>& set) +{ + string r; + r.reserve (s.size ()); + set.clear (); + + size_t p (string::npos); + + for (size_t i (0), n (s.size ()); i < n; ++i) + { + if (p == string::npos && s[i] == '<') + { + p = i; + r += "\\ci{"; + continue; + } + + if (p != string::npos && s[i] == '>') + { + set.insert (string (s, p + 1, i - p - 1)); + r += '}'; + p = string::npos; + continue; + } + + if (p != string::npos && s[i] == '}' && s[i - 1] != '\\') + { + r += "\\}"; + continue; + } + + r += s[i]; + } + + return r; +} + +string context:: +translate (string const& s, std::set<string> const& set) +{ + string r; + r.reserve (s.size ()); + + bool pre (false); + size_t p (string::npos); + + for (size_t i (0), n (s.size ()); i < n; ++i) + { + char c (s[i]); + + // Skip pre-formatted fragments. + // + if (c == (pre ? 0x03 : 0x02)) + { + pre = !pre; + + if (p != string::npos) + { + assert (pre); + r.append (s, p, i - p); + p = string::npos; + } + } + else if (!pre) + { + if (p == string::npos && c == '<') + { + p = i; + continue; + } + + if (p != string::npos) + { + if (c == '>') + { + string a (s, p + 1, i - p - 1); + + if (set.find (a) != set.end ()) + { + r += "\\ci{"; + + for (size_t j (0), n (a.size ()); j < n; ++j) + { + if (a[j] == '}' && (j == 0 || a[j - 1] != '\\')) + r += "\\}"; + else + r += a[j]; + } + + r += '}'; + } + else + { + r += '<'; + r += a; + r += '>'; + } + p = string::npos; + } + continue; + } + } + + r += c; + } + + // If we found the opening '<' but no closing '>', add the rest. + // + if (p != string::npos) + r.append (s, p, string::npos); + + return r; +} + +void context:: +format_line (output_type ot, string& r, const char* l, size_t n) +{ + bool color (options.ansi_color ()); + + typedef unsigned char span; // Mask. + + const span code = 1; + const span itlc = 2; + const span bold = 4; + const span link = 8; + const span note = 16; + + vector<span> spans; + + string link_target; + string link_section; // If not empty, man section; target is man name. + bool link_empty (false); // Link has no text. + + bool escape (false); + for (size_t i (0); i < n; ++i) + { + char c (l[i]); + + if (escape) + { + bool new_span (false); + + switch (c) + { + case ' ': + { + // Non-ignorable space. + // + switch (ot) + { + case ot_plain: + { + r += ' '; + break; + } + case ot_html: + { + r += " "; + break; + } + case ot_man: + { + r += "\\ "; + break; + } + } + + break; + } + case '-': + { + // N-dash. If someone wants m-dash, can use \-- with \-{}- as + // a way to "escape" an n-dash followed by hyphen. + // + switch (ot) + { + case ot_plain: + { + r += "--"; + break; + } + case ot_html: + { + r += "–"; + break; + } + case ot_man: + { + r += "\\(en"; + break; + } + } + + break; + } + case 'n': + { + switch (ot) + { + case ot_plain: + { + r += '\n'; + break; + } + case ot_html: + { + if (!r.empty () && r[r.size () - 1] != '\n') + r += '\n'; + + r += "<br/>"; + break; + } + case ot_man: + { + if (!r.empty () && r[r.size () - 1] != '\n') + r += '\n'; + + // Note that if we have several consecutive breaks, they + // will be collapsed into a single one. No, .sp doesn't + // work (or, more exactly, will only work for two breaks). + // + r += ".br"; + break; + } + } + + // Skip following spaces. + // + for (; i + 1 < n && l[i + 1] == ' '; ++i) ; + + switch (ot) + { + case ot_plain: break; + case ot_html: + case ot_man: + { + if (i + 1 < n) // More text in this paragraph? + r += "\n"; + + break; + } + } + + break; + } + case 'c': + { + span s (code); + size_t j (i + 1); + + if (j < n) + { + if (l[j] == 'i') + { + s |= itlc; + j++; + + if (j < n && l[j] == 'b') + { + s |= bold; + j++; + } + } + else if (l[j] == 'b') + { + s |= bold; + j++; + + if (j < n && l[j] == 'i') + { + s |= itlc; + j++; + } + } + } + + if (j < n && l[j] == '{') + { + i = j; + spans.push_back (s); + new_span = true; + break; + } + + r += 'c'; + break; + } + case 'i': + { + span s (itlc); + size_t j (i + 1); + + if (j < n) + { + if (l[j] == 'c') + { + s |= code; + j++; + + if (j < n && l[j] == 'b') + { + s |= bold; + j++; + } + } + else if (l[j] == 'b') + { + s |= bold; + j++; + + if (j < n && l[j] == 'c') + { + s |= code; + j++; + } + } + } + + if (j < n && l[j] == '{') + { + i = j; + spans.push_back (s); + new_span = true; + break; + } + + r += 'i'; + break; + } + case 'b': + { + span s (bold); + size_t j (i + 1); + + if (j < n) + { + if (l[j] == 'c') + { + s |= code; + j++; + + if (j < n && l[j] == 'i') + { + s |= itlc; + j++; + } + } + else if (l[j] == 'i') + { + s |= itlc; + j++; + + if (j < n && l[j] == 'c') + { + s |= code; + j++; + } + } + } + + if (j < n && l[j] == '{') + { + i = j; + spans.push_back (s); + new_span = true; + break; + } + + r += 'b'; + break; + } + case 'l': + { + if (i + 1 < n && l[i + 1] == '{') + { + string& t (link_target); + + if (!t.empty ()) + { + cerr << "error: nested links in documentation paragraph '" + << string (l, 0, n) << "'" << endl; + throw generation_failed (); + } + + // Find the end of the link target. + // + size_t b (++i + 1), e (b); + for (; i + 1 < n; ++i) + { + char c (l[i + 1]); + + if (c == ' ' || c == '}') + { + e = i + 1; + + if (c == ' ') // Skip spaces. + for (++i; i + 1 < n && l[i + 1] == ' '; ++i) ; + + break; + } + } + + // Run the link target through format_line(ot_plain) to handle + // escaping (e.g., \\$). + // + format_line (ot_plain, t, l + b, e - b); + + if (t.empty ()) + { + cerr << "error: missing link target in documentation paragraph '" + << string (l, 0, n) << "'" << endl; + throw generation_failed (); + } + + // See if link_target is a man page name/section. + // + size_t o (t.find ('(')); + + if (o != string::npos) + { + link_section.assign (t, o + 1, t.size () - o - 2); + + if (t[t.size () - 1] != ')' || link_section.empty ()) + { + cerr << "error: missing man section in '" << t << "'" << endl; + throw generation_failed (); + } + + string n (t, 0, o); + + if (n.empty ()) + { + cerr << "error: missing man page in '" << t << "'" << endl; + throw generation_failed (); + } + + t = n; + } + else + link_section.clear (); + + // If this is a local fragment reference, add it to the set to be + // verified at the end. + // + if (t[0] == '#') + { + assert (link_section.empty ()); // Not a man page. + ref_set.insert (string (t, 1, string::npos)); + } + + link_empty = i + 1 < n && l[i + 1] == '}'; + + spans.push_back (link); + new_span = true; + break; + } + + r += 'l'; + break; + } + case 'N': + { + if (i + 1 < n && l[i + 1] == '{') + { + ++i; + spans.push_back (note); + new_span = true; + break; + } + + r += 'N'; + break; + } + case '\\': + { + switch (ot) + { + case ot_man: + { + r += "\\e"; + break; + } + default: + { + r += '\\'; + break; + } + } + break; + } + case '"': + { + r += '"'; + break; + } + case '\'': + { + r += '\''; + break; + } + case '}': + { + r += '}'; + break; + } + case '|': + { + r += '|'; + break; + } + default: + { + cerr << "error: unknown escape sequence '\\" << c << "' in " + << "documentation paragraph '" << string (l, 0, n) << "'" + << endl; + throw generation_failed (); + } + } + + // If we just added a new span, add corresponding output markup. + // + if (new_span) + { + span s (spans.back ()); + + span es (0); // Effective span. + for (vector<span>::iterator i (spans.begin ()); + i != spans.end (); + ++i) + es |= *i & (code | itlc | bold); + + switch (ot) + { + case ot_plain: + { + if ((s & note) != 0) + { + r += "[Note: "; + } + else if ((s & link) == 0) + { + if (color) + { + if (s & bold) + r += "\033[1m"; + + if (s & itlc) + r += "\033[4m"; + } + } + + break; + } + case ot_html: + { + if (s & note) + { + r += "<span class=\"note\">"; + } + else if (s & link) + { + r += "<a href=\""; + + // It might be useful to include the man section into the regex + // somehow. + // + string t (link_section.empty () + ? link_target + : link_target + options.html_suffix ()); + + string pt (process_link_target (t)); + + if (pt.empty ()) + { + cerr << "error: link '" << t << "' became empty" << endl; + throw generation_failed (); + } + + r += pt; + r += "\">"; + } + else + { + if (s & code) + r += "<code>"; + + if (s & itlc) + r += "<i>"; + + if (s & bold) + r += "<b>"; + } + + break; + } + case ot_man: + { + if ((s & note) != 0) + { + cerr << "error: \\N{} in man output not yet supported" << endl; + throw generation_failed (); + } + + if ((s & link) == 0) + { + if ((es & itlc) && (es & bold)) + r += "\\f(BI"; + else if (es & itlc) + r += "\\fI"; + else if (es & bold) + r += "\\fB"; + } + + break; + } + } + } + + escape = false; + } + else // Not escape. + { + switch (c) + { + case '\\': + { + escape = true; + break; + } + case '.': + { + if (ot == ot_man) + r += "\\."; + else + r += '.'; + break; + } + case '}': + { + if (!spans.empty ()) + { + span s (spans.back ()); + spans.pop_back (); + + span es (0); // New effective span. + for (vector<span>::iterator i (spans.begin ()); + i != spans.end (); + ++i) + es |= *i & (code | itlc | bold); + + switch (ot) + { + case ot_plain: + { + if ((s & note) != 0) + { + r += ']'; + } + else if ((s & link) != 0) + { + string t (link_section.empty () + ? link_target + : link_target + "(" + link_section + ")"); + + string pt (process_link_target (t)); + + if (pt.empty ()) + { + if (link_empty) + { + cerr << "error: link target '" << t << "' became empty " + << "and link text is also empty" << endl; + throw generation_failed (); + } + } + else + { + if (!link_empty) + r += " ("; + + if (link_section.empty ()) + r += pt; + else + { + if (color) + r += "\033[1m"; + + r += pt; + + if (color) + r += "\033[0m"; + } + + if (!link_empty) + r += ")"; + } + } + else + { + if (color) + { + // While there are codes to turn off bold (22) and + // underline (24), it is not clear how widely they + // are supported. + // + r += "\033[0m"; // Clear all. + + if (es & bold) + r += "\033[1m"; + + if (es & itlc) + r += "\033[4m"; + } + } + + break; + } + case ot_html: + { + if ((s & note) != 0) + { + r += "</span>"; + } + else if ((s & link) != 0) + { + if (link_empty) + { + if (link_section.empty ()) + r += link_target; + else + { + r += "<code><b>"; + r += link_target + "(" + link_section + ")"; + r += "</b></code>"; + } + } + + r += "</a>"; + } + else + { + if (s & bold) + r += "</b>"; + + if (s & itlc) + r += "</i>"; + + if (s & code) + r += "</code>"; + } + + break; + } + case ot_man: + { + assert ((s & note) == 0); + + if ((s & link) != 0) + { + string t (link_section.empty () + ? link_target + : link_target + "(" + link_section + ")"); + + string pt (process_link_target (t)); + + if (pt.empty ()) + { + if (link_empty) + { + cerr << "error: link target '" << t << "' became empty " + << "and link text is also empty" << endl; + throw generation_failed (); + } + } + else + { + if (!link_empty) + r += " ("; + + if (link_section.empty ()) + r += pt; + else + r += "\\fB" + pt + "\\fP"; + + if (!link_empty) + r += ")"; + } + } + else + { + // At first sight, \fP (select previous font) looks like + // exactly what we need here. However, it doesn't quite + // have the stack semantics that we need. + // + if ((es & itlc) && (es & bold)) + r += "\\f(BI"; + else if (es & itlc) + r += "\\fI"; + else if (es & bold) + r += "\\fB"; + else + r += "\\fR"; + } + + break; + } + } + + if (s & link) + link_target.clear (); + + break; + } + } + // Fall through. + default: + r += c; + break; + } + } + } + + if (escape) + { + cerr << "error: unterminated escape sequence in documentation " + << "paragraph '" << string (l, 0, n) << "'" << endl; + throw generation_failed (); + } + + if (!spans.empty ()) + { + unsigned char b (spans.back ()); + string bs; + + if (b & code) bs += 'c'; + if (b & itlc) bs += 'i'; + if (b & bold) bs += 'b'; + if (b & link) bs = 'l'; + if (b & note) bs = 'N'; + + + cerr << "error: unterminated formatting span '\\" << bs << "' " + << "in documentation paragraph '" << string (l, 0, n) << "'" << endl; + throw generation_failed (); + } +} + +struct block +{ + // The semantic meaning of \hN and their mapping to HTML is as follows: + // + // \h0 - preface <h1 class="preface"> + // \H - part <h1 class="part"> + // \h1 - chapter <h1> + // \h - section <h1> or <h2> + // \h2 - sub-section <h3> + // + // In HTML, if \h0 or \h1 was seen, then \h is automatically mappend to + // <h2>. Otherwise it is <h1>. + // + // The specifier (0, H, 1, h, 2) is stored in header. + // + enum kind_type {h, ul, ol, dl, li, note, text, pre}; + + kind_type kind; + bool para; // True if first text fragment should be in own paragraph. + + string id; + string header; + string value; + string trailer; + + block (kind_type k, bool p, const string& i, const string& h = "") + : kind (k), para (p), id (i), header (h) {} +}; + +static const char* block_kind_str[] = { + "\\h", "\\ul", "\\ol", "\\dl", "\\li", "\\N", "text", "preformatted text"}; + +inline ostream& +operator<< (ostream& os, block::kind_type k) +{ + return os << block_kind_str[k]; +} + +// Given the contents of a block element, convert any leading/trailing <br/> +// elements to top/bottom margins. While conceptually it would seem better to +// translate them to padding, margins actually give better practical results +// since they overlap with container's margins. +// +static string +html_margin (string& v) +{ + size_t top (0), bot (0); + + const char* b (v.c_str ()); + const char* e (v.c_str () + v.size ()); + + for (; e - b >= 5 && strncmp (b, "<br/>", 5) == 0; ++top) + { + b += 5; + + if (b != e && *b == '\n') // Remove following newline, if any. + ++b; + } + + for (; e - b >= 5 && strncmp (e - 5, "<br/>", 5) == 0; ++bot) + { + e -= 5; + + if (e != b && *(e - 1) == '\n') // Remove preceding newline, if any. + --e; + } + + if (top == 0 && bot == 0) + return ""; + + string t; + t.swap (v); + v.assign (b, e - b); + + ostringstream os; + + if (top != 0) + os << "margin-top:" << top << "em"; + + if (bot != 0) + os << (top != 0 ? ";" : "") << "margin-bottom:" << bot << "em"; + + return os.str (); +} + +// The same idea except there are no margins. So we just strip .br. +// +static void +man_margin (string& v) +{ + size_t top (0), bot (0); + + const char* b (v.c_str ()); + const char* e (v.c_str () + v.size ()); + + for (; e - b >= 3 && strncmp (b, ".br", 3) == 0; ++top) + { + b += 3; + + if (b != e && *b == '\n') // Remove following newline, if any. + ++b; + } + + for (; e - b >= 3 && strncmp (e - 3, ".br", 3) == 0; ++bot) + { + e -= 3; + + if (e != b && *(e - 1) == '\n') // Remove preceding newline, if any. + --e; + } + + if (top != 0 || bot != 0) + { + string t; + t.swap (v); + v.assign (b, e - b); + } +} + +string context:: +format (semantics::scope& scope, string const& s, bool para) +{ + stack<block> blocks; + blocks.push (block (block::text, para, "")); // Top-level. + + // Number of li in ol. Since we don't support nested lists, we don't + // need to push it into the stack. + // + size_t ol_count (0); + + // Mapping of \h to HTML tag. By default it is <h1> until we encounter + // \h0 or \h1 at which point we change it to <h2>. + // + char html_h ('1'); + + bool last (false); + for (size_t b (0), e; !last; b = e + 1) + { + bool pre (s[b] == 0x02); + + const char* l; + size_t n; + string subst; // Variable-substituted. + + if (pre) + { + ++b; // Skip 0x02. + + e = s.find (0x03, b); + assert (e != string::npos); + + l = s.c_str () + b; + n = e - b; + + ++e; // Skip newline that follows 0x03. + last = (e == s.size ()); + } + else + { + e = s.find ('\n', b); + last = (e == string::npos); + + l = s.c_str () + b; + n = (last ? s.size () : e) - b; + + // 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); + + // First determine what kind of paragraph block this is. + // + block::kind_type k (block::h); + string id; + string header; + string trailer; + + size_t pop (0); // Pop count. + + if (pre) + { + k = block::pre; + } + else + { + // \h \H + // + if (n >= 3 && + l[0] == '\\' && + (l[1] == 'h' || l[1] == 'H') && + (l[2] == '|' || l[2] == '#')) + { + k = block::h; + header = l[1]; + l += 3; + n -= 3; + } + // + // \h0 \h1 \h2 + // + else if (n >= 4 && + l[0] == '\\' && + l[1] == 'h' && + (l[3] == '|' || l[3] == '#')) + { + if (l[2] != '0' && l[2] != '1' && l[2] != '2') + { + cerr << "error: '0', '1', or '2' expected in \\hN| in '" + << string (ol, 0, on) << "'" << endl; + throw generation_failed (); + } + + k = block::h; + header = l[2]; + l += 4; + n -= 4; + } + // + // \ul \ol \dl + // + else if (n >= 4 && + l[0] == '\\' && + (l[1] == 'u' || l[1] == 'o' || l[1] == 'd') && + l[2] == 'l' && + (l[3] == '|' || l[3] == '#')) + { + switch (l[1]) + { + case 'u': k = block::ul; break; + case 'o': k = block::ol; break; + case 'd': k = block::dl; break; + } + + l += 4; + n -= 4; + } + // + // \li + // + else if (n >= 4 && + l[0] == '\\' && + l[1] == 'l' && + l[2] == 'i' && + (l[3] == '|' || l[3] == '#')) + { + k = block::li; + l += 4; + n -= 4; + } + // + // \N (note) + // + else if (n >= 3 && + l[0] == '\\' && + l[1] == 'N' && + (l[2] == '|' || l[2] == '#')) + { + k = block::note; + l += 3; + n -= 3; + } + else + k = block::text; + + // Get the id, if present. + // + if (k != block::text && *(l - 1) == '#') + { + for (; n != 0 && *l != '|'; ++l, --n) + id += *l; + + if (n == 0) + { + cerr << "error: paragraph begin '|' expected after id in '" + << string (ol, 0, on) << "'" << endl; + throw generation_failed (); + } + + ++l; --n; // Skip '|'. + } + + // Skip leading spaces after opening '|'. + // + if (k != block::text) + while (n != 0 && (*l == 0x20 || *l == 0x0D || *l == 0x09)) {++l; --n;} + + // Next figure out how many blocks we need to pop at the end of this + // paragraph. Things get a bit complicated since '|' could be escaped. + // + for (; n - pop > 0 && l[n - pop - 1] == '|'; ++pop) ; + if (pop != 0) + { + // To determine whether the first '|' is part of an escape sequence + // we have to find the first non-backslash character and then figure + // out who escapes whom. + // + size_t ec (0); // Escape count. + for (; n - pop - ec > 0 && l[n - pop - ec - 1] == '\\'; ++ec) ; + + // If we have an odd number of backslashes, then the last '|' is + // escaped. + // + if (ec % 2 != 0) + --pop; + + n -= pop; // Number of special '|' at the end. + + // Skip trailing spaces before closing '|'. + // + while (n != 0 && (l[n - 1] == 0x20 || + l[n - 1] == 0x0D || + l[n - 1] == 0x09)) n--; + } + } + + // Outer block kind. + // + block::kind_type ok (blocks.top ().kind); + + // Verify that this block type is valid in this context. Ignore + // empty text blocks (can happen if we just have '|'). + // + if (k != block::text || n != 0) + { + bool good (true); + + switch (ok) + { + case block::h: good = false; break; + case block::ul: + case block::ol: + case block::dl: good = (k == block::li); break; + case block::li: good = (k == block::note || + k == block::text || + k == block::pre ); break; + case block::note: good = (k == block::text || + k == block::pre || + (ot == ot_html && (k == block::ul || + k == block::ol || + k == block::dl))); break; + case block::text: good = (k != block::li); break; + case block::pre: assert (false); + } + + if (!good) + { + cerr << "error: " << k << " inside " << ok << " " + << "in documentation string '" << s << "'" << endl; + throw generation_failed (); + } + } + + // Check id for duplicates. Do it only on the non-TOC pass. + // + if (!toc && !id.empty ()) + { + if (!id_set.insert (id).second) + { + cerr << "error: duplicate id '" << id << "' in documentation " + << "string '" << s << "'" << endl; + throw generation_failed (); + } + } + + // Verify the block itself. + // + switch (k) + { + case block::h: + { + // \h blocks are only valid if we are required to start a new + // paragraph (first_para is true). + // + if (!para) + { + cerr << "error: paragraph '" << string (ol, 0, on) << "' " + << "not allowed in '" << s << "'" << endl; + throw generation_failed (); + } + + // \h must be single-paragraph. + // + if (pop == 0) + { + cerr << "error: '|' expected at the end of paragraph '" + << string (ol, 0, on) << "'" << endl; + throw generation_failed (); + } + + // \h must not be empty. + // + if (n == 0) + { + cerr << "error: empty paragraph '" << string (ol, 0, on) << "' " + << "in documentation string '" << s << "'" << endl; + throw generation_failed (); + } + + break; + } + case block::ul: + case block::ol: + case block::dl: + { + if (pop != 0) + { + cerr << "error: empty list '" << string (ol, 0, on) << "' " + << "in documentation string '" << s << "'" << endl; + throw generation_failed (); + } + + if (n != 0) + { + cerr << "error: unexpected text after " << k << "| " + << "in paragraph '" << string (ol, 0, on) << "'" << endl; + throw generation_failed (); + } + + break; + } + case block::li: + { + if (ok == block::dl) + { + if (n == 0) + { + cerr << "error: term text missing in paragraph '" + << string (ol, 0, on) << "'" << endl; + throw generation_failed (); + } + } + + break; + } + case block::note: + case block::text: + case block::pre: + break; + } + + // Push the block into the stack. + // + switch (k) + { + case block::h: blocks.push (block (k, false, id, header)); break; + case block::ul: + case block::ol: ol_count = 0; // Fall through. + case block::dl: blocks.push (block (k, true, id)); break; + case block::li: + { + switch (blocks.top ().kind) + { + case block::ol: + { + ostringstream os; + os << ++ol_count; + header = os.str (); + break; + } + case block::dl: + { + format_line (ot, header, l, n); + n = 0; + break; + } + default: + break; + } + + blocks.push (block (k, false, id, header)); + break; + } + case block::note: + { + blocks.push (block (k, true, id, header)); + break; + } + case block::text: break; // No push. + case block::pre: break; // No push. + } + + // 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 ()); + string& v (b.value); + bool first (v.empty ()); + + switch (ot) + { + case ot_plain: + { + // Separate paragraphs with a blank line. + // + if (!first) + v += "\n\n"; + + if (k == block::pre) + v.append (l, n); + else + { + /* + // Strip a single "\n" at the beginning of the paragraph since we + // already wrote one newline as part of starting the paragraph. An + // example where this might be useful: + // + // li| + // + // \ + // ... + // \ + // + // \n| + // + if (n >= 2 && l[0] == '\\' && l[1] == 'n') + { + l += 2; + n -= 2; + } + + if (n != 0) + */ + + format_line (ot, v, l, n); + } + + break; + } + case ot_html: + { + // Separate paragraphs with a blank line. + // + if (!first) + v += "\n\n"; + + if (k == block::pre) + { + v += "<pre>"; + v.append (l, n); + v += "</pre>"; + } + else + { + if (!first || b.para) + { + string t; + format_line (ot, t, l, n); + string s (html_margin (t)); + + // If the entire paragraph is wrapped in <code> then signal this + // fact via the "code" class. This can be used, for example, to + // change alignment from "justify" to "left" in order to support + // custom alignments (in combination with non-ignorable space + // escaping; think multi-line synopsis in man pages). + // + // While at it also cleanup nested <code></code> fragments (we + // may end up with quite a few of them due to the <arg> + // translation). + // + string c; + { + size_t n (t.size ()); + + if (n >= 13 && + t.compare (0, 6, "<code>") == 0 && + t.compare (n - 7, 7, "</code>") == 0) + { + string tt; + + // Make sure this is one contigous <code>. + // + size_t b (1); // <code>/</code> balance. + for (size_t i (6), j (0); + i != n && (i = t.find ("code>", i)) != string::npos; + j = i = i + 5) + { + if (t[i - 1] == '<') b++; + else if (t[i - 1] == '/' && t[i - 2] == '<') b--; + else continue; + + // Append previous chunk. + // + tt.append (t, j, i - j - (t[i - 1] == '<' ? 1 : 2)); + + if (b == 0) + { + if (i + 5 == n) + { + tt.append (t, i - 2, 7); // Closing </code>. + t = move (tt); + c = "code"; + } + + break; + } + } + } + } + + v += "<p"; + if (!c.empty ()) v += " class=\"" + c + "\""; + if (!s.empty ()) v += " style=\"" + s + "\""; + v += ">"; + v += t; + v += "</p>"; + } + else + format_line (ot, v, l, n); + } + + break; + } + case ot_man: + { + if (b.para) + { + if (!first) + v += "\n"; + + v += ".PP\n"; + } + else + { + if (!first) + v += "\n\n"; + } + + if (k == block::pre) + { + v += ".nf\n"; + + // Note that if we have several consecutive blank lines, they + // will be collapsed into a single one. No, .sp doesn't work. + // + char c, p ('\n'); // Current and previous characters. + for (size_t i (0); i != n; p = c, ++i) + { + switch (c = l[i]) + { + case '\\': v += '\\'; break; + case '.': v += p != '\n' ? "\\" : "\\&\\"; break; + } + + v += c; + } + + v += "\n.fi"; + } + else + { + format_line (ot, v, l, n); + } + + break; + } + } + } + + // Pop paragraph blocks. + // + if (pop >= blocks.size ()) // >= to account for top-level. + { + cerr << "error: extraneous '|' at the end of paragraph '" + << string (ol, 0, on) << "'" << endl; + throw generation_failed (); + } + + for (; pop != 0; --pop) + { + block pb (blocks.top ()); // move + string& pi (pb.id); + string& ph (pb.header); + string& pv (pb.value); + + blocks.pop (); + + 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]); + + if (pi.empty ()) + { + cerr << "error: TOC heading '" << pv << "' has no id" << endl; + throw generation_failed (); + } + + pv = "<a href=\"#" + pi + "\">" + pv + "</a>"; + + // 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' || 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, ' ') + "</table>\n"; + v += string (l * 4 - 4, ' ') + "</td></tr>\n"; + } + else + // Otherwise it is inline. + // + v += "</td></tr>\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 + "<table class=\"toc\">\n"; + } + + in += " "; + + switch (t) + { + case 'H': + { + v += in + "<tr><th colspan=\"2\">" + pv + "</th></tr>\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 + "<tr>"; + + if (!n.empty ()) + { + v += "<th>" + n + "</th><td>"; + heading_map[pi] = n; // Save it for later. + } + else + v += "<td class=\"preface\" colspan=\"2\">"; + + v += 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 (t == '0' || t == '1') + html_h = '2'; + + break; + } + case ot_man: break; + } + } + + continue; + } + + switch (ot) + { + case ot_plain: + { + const char* sn (v.empty () ? "" : "\n"); + const char* dn (v.empty () ? "" : "\n\n"); + + switch (pb.kind) + { + case block::h: + { + v += dn; + + if (options.ansi_color ()) + v += "\033[1m"; // Bold. + + v += pv; + + if (options.ansi_color ()) + v += "\033[0m"; + + break; + } + case block::ul: + case block::ol: + case block::dl: v += dn + pv; break; + case block::li: + { + v += sn; + size_t ind (0), vn (pv.size ()); + + switch (b.kind) + { + case block::ul: + v += "* "; + ind = 2; + break; + case block::ol: + v += ph + ". "; + ind = ph.size () + 2; + break; + case block::dl: + v += ph + "\n" + (vn != 0 && pv[0] != '\n' ? " " : ""); + ind = 4; + break; + default: + break; + } + + // Add value with indentation for subsequent paragraphs. + // + char c, p ('\0'); // Current and previous characters. + for (size_t i (0); i != vn; p = c, ++i) + { + c = pv[i]; + + if (p == '\n' && c != '\n') // Don't indent blanks. + v += string (ind, ' '); + + v += c; + } + + break; + } + case block::note: + { + v += dn; + + // Add a special character (bell) plus space in front of every + // line (including blanks). The bell is recognized and + // translated by txt_wrap_lines() to '|'. + // + char p ('\n'); // Previous character. + for (size_t i (0), n (pv.size ()); i != n; ++i) + { + char c (pv[i]); + + if (p == '\n') + v += (c != '\n' ? "\x07 " : "\x07"); + + v += c; + p = c; + } + + break; + } + case block::text: + case block::pre: assert (false); + } + + break; + } + case ot_html: + { + if (!v.empty ()) + v += "\n\n"; + + switch (pb.kind) + { + case block::h: + { + char t (ph[0]); + + string h; + string c; + + typedef map<char, string> map; + const map& hm (options.html_heading_map ()); + map::const_iterator mi (hm.find (t)); + + if (mi == hm.end ()) + { + switch (t) + { + case '0': h = "h1"; c = "preface"; break; + case 'H': h = "h1"; c = "part"; break; + case '1': h = "h1"; break; + case 'h': h = html_h == '1' ? "h1" : "h2"; break; + case '2': h = "h3"; break; + } + } + else + h = mi->second; + + v += '<' + h; + if (!pi.empty ()) v += " id=\"" + pi + '"'; + if (!c.empty ()) v += " class=\"" + c + '"'; + v += '>'; + + // See if we have a heading number (assigned by TOC). + // + if (!pi.empty ()) + { + heading_map_type::const_iterator i (heading_map.find (pi)); + + if (i != heading_map.end ()) + v += i->second + ' '; + } + + v += pv; + + v += "</" + h + '>'; + + // @@ This only works for a single string fragment. + // + if (t == '0' || t == '1') + html_h = '2'; + + break; + } + case block::ul: v += "<ul>\n" + pv + "\n</ul>"; break; + case block::ol: v += "<ol>\n" + pv + "\n</ol>"; break; + case block::dl: v += "<dl>\n" + pv + "\n</dl>"; break; + case block::li: + { + string pvs (html_margin (pv)); + + if (b.kind == block::dl) + { + string phs (html_margin (ph)); + + v += (phs.empty () ? "<dt>" : "<dt style=\"" + phs + "\">") + + ph + "</dt>\n"; + + v += (pvs.empty () ? "<dd>" : "<dd style=\"" + pvs + "\">") + + pv + "</dd>"; + } + else + v += (pvs.empty () ? "<li>" : "<li style=\"" + pvs + "\">") + + pv + "</li>"; + + break; + } + case block::note: + { + v += "<div class=\"note\">\n"; + v += pv; + v += "\n</div>"; + + break; + } + case block::text: + case block::pre: assert (false); + } + + break; + } + case ot_man: + { + // Seeing that we always write a macro, one newline is enough. + // + if (!v.empty ()) + v += "\n"; + + switch (pb.kind) + { + case block::h: v += ".SH \"" + pv + "\""; break; + case block::ul: + case block::ol: + case block::dl: + { + if (!b.para) // First list inside .IP. + { + // .IP within .IP? Just shoot me in the head already! We + // have to manually indent it with .RS/.RE *and* everything + // that comes after it (since .PP resets the indent). Why + // not just indent the whole list content? Because then the + // first line will never start on the same line as the term. + // + v += ".RS\n"; + b.trailer = "\n.RE"; + b.para = true; // Start emitting .PP from now on. + } + + v += pv; + break; + } + case block::li: + { + man_margin (ph); // Strip leading/trailing .br. + + switch (b.kind) + { + case block::ul: v += ".IP \\(bu 2em\n" + pv; break; + case block::ol: v += ".IP " + ph + ". 4em\n" + pv; break; + // + // Add .br to force the definition to start on the new line. + // + case block::dl: v += ".IP \"" + ph + "\"\n.br\n" + pv; break; + default: break; + } + + break; + } + case block::note: + { + cerr << "error: " << pb.kind << "| in man output not yet " + << "supported" << endl; + throw generation_failed (); + } + case block::text: + case block::pre: assert (false); + } + + break; + } + } + } + } + + assert (!blocks.empty ()); // Should have top-level. + + if (blocks.size () > 1) + { + cerr << "error: unterminated paragraph " << blocks.top ().kind << " " + << "in documentation string '" << s << "'" << endl; + throw generation_failed (); + } + + block& b (blocks.top ()); + + switch (ot) + { + case ot_man: return b.value + b.trailer; + default: return b.value; + } +} + +string context:: +start_toc () +{ + switch (ot) + { + case ot_plain: break; + case ot_html: + { + tocs.push_back (toc_entry ('\0')); + return " <table class=\"toc\">"; + } + case ot_man: break; + } + + return string (); +} + +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, ' ') + "</table>\n"; + v += string (l * 4 - 4, ' ') + "</td></tr>\n"; + } + else + // Otherwise it is inline. + // + v += "</td></tr>\n"; + } + + v += " </table>"; + return v; + } + case ot_man: break; + } + + return string (); +} + +void context:: +verify_id_ref () +{ + if (!options.omit_link_check ()) + { + bool f (false); + + for (id_set_type::const_iterator i (ref_set.begin ()); + i != ref_set.end (); + ++i) + { + if (id_set.find (*i) == id_set.end ()) + { + cerr << "error: no id for fragment link '#" << *i << "'" << endl; + f = true; + } + } + + if (f) + throw generation_failed (); + } + + id_set.clear (); + ref_set.clear (); + heading_map.clear (); +} + +string context:: +substitute (const string& s, const path* d) +{ + string r; + + // Scan the string looking for variables ($var$) or paths. + // + size_t b (0), e (b); + for (size_t n (s.size ()); e != n; ++e) + { + if (s[e] == '$' && e + 1 != n) + { + if (s[e + 1] == '$') // Escape. + { + r.append (s, b, ++e - b); // Keep one, skip the other. + b = e + 1; + continue; + } + + bool file (false); + + // Scan for as long as it is a C identifier or a file. + // + size_t p (e + 1); // Position of the second '$'. + for (; p != n; ++p) + { + char c (s[p]); + bool f (p == e + 1); // First. + + if (d != 0 && f && c == '.' && p + 1 != n && + (s[p + 1] == '/' || // "./" + (s[p + 1] == '.' && p + 2 != n && s[p + 2] == '/'))) // "../" + file = true; + + if (file) + { + if (c == '$') + break; + } + else + { + if (!(c == '_' || + ('a' <= c && c <= 'z') || + ('A' <= c && c <= 'Z') || + (!f && '0' <= c && c <= '9'))) + break; + } + } + + // Note: check that the second '$' is not escaped. + // + if (p != n && s[p] == '$' && (p + 1 == n || s[p + 1] != '$')) + { + r.append (s, b, e - b); // Save what came before the expansion. + + // Var/file name. + // + ++e; + string v (s, e, p - e); + + if (file) + { + path p (*d / path (v)); + p.normalize (); // Apply '.' and '..'. + + ifstream ifs (p.string ().c_str (), ifstream::in | ifstream::binary); + if (!ifs.is_open ()) + { + cerr << p << ": error: unable to open in read mode" << endl; + throw generation_failed (); + } + + // We don't expect our file to contain '\0' so use that as the + // delimiter to read the entire file with getline(). + // + string s; + getline (ifs, s, '\0'); + r += s; + } + 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. + // + using semantics::doc; + + if (doc* d = unit.lookup<doc> ("", "var: " + v)) + r += d->front (); + else + { + cerr << "error: undefined variable '" << v << "' in '" << s << "'" + << endl; + throw generation_failed (); + } + } + + e = p; + b = e + 1; + } + } + } + + r.append (s, b, e - b); // Last chunk. + return r; +} + +bool context:: +substitute (semantics::scope& scope, const char* s, size_t n, string& result) +{ + bool sub (false); + result.clear (); + + // Scan the string looking for variables (\$var$). + // + size_t b (0), e (b); + for (char p ('\0'); e != n; ++e) + { + char c (s[e]); + + if (p == '\\') + { + if (c == '\\') // Escape sequence. + { + p = '\0'; + continue; + } + + if (c == '$') + { + // Variable expansion. + // + sub = true; + + // Find the closing '$'. + // + size_t p (e + 1); // Position of the second '$'. + for (; p != n && s[p] != '$'; ++p) ; + + if (p == n) + { + cerr << "error: missing closing '$' in '" << string (s, n) << "'" + << endl; + throw generation_failed (); + } + + result.append (s, b, e - b - 1); // Save what came before. + + // Var name. + // + ++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; + + if (doc* d = unit.lookup<doc> (scope.fq_name (), "var: " + v)) + result += d->front (); + else + { + cerr << "error: undefined variable '" << v << "' in '" + << string (s, n) << "'" << endl; + throw generation_failed (); + } + + e = p; + b = e + 1; + } + } + + p = s[e]; + } + + if (sub) + result.append (s, b, e - b); // Last chunk. + + return sub; +} + +string context:: +fq_name (semantics::nameable& n, bool cxx_name) +{ + using namespace semantics; + + string r; + + if (dynamic_cast<cli_unit*> (&n)) + { + return ""; // Map to global namespace. + } + else + { + r = fq_name (n.scope ()); + r += "::"; + r += cxx_name ? escape (n.name ()) : n.name (); + } + + return r; +} + +string context:: +ns_open (const string& qn, bool last) +{ + string::size_type b (0), e; + + do + { + e = qn.find ("::", b); + string n (qn, b, e == string::npos ? e : e - b); + + if (!n.empty () && (last || e != string::npos)) + os << "namespace " << n << "{"; + + b = e; + + if (b == string::npos) + return n; + + b += 2; + + } while (true); +} + +void context:: +ns_close (const string& qn, bool last) +{ + string::size_type b (0), e; + + do + { + e = qn.find ("::", b); + string n (qn, b, e == string::npos ? e : e - b); + + if (!n.empty () && (last || e != string::npos)) + os << "}"; + + b = e; + + if (b == string::npos) + return; + + b += 2; + + } while (true); +} + +class_doc context:: +class_doc (semantics::class_& c) +{ + typedef map<string, string> map; + const map& m (options.class_doc ()); + + string n (c.fq_name ()); + + map::const_iterator i (m.find (n)); + + if (i == m.end ()) + { + n = string (n, 2, string::npos); // Try without leading '::'. + i = m.find (n); + } + + if (i == m.end ()) + return cd_default; + + const string& k (i->second); + + if (k == "exclude") + return cd_exclude; + if (k == "exclude-base") + return cd_exclude_base; + else if (k == "short") + return cd_short; + else if (k == "long") + return cd_long; + else + { + cerr << "error: unknown --class-doc kind value '" << k << "'" << endl; + throw generation_failed (); + } +} + +string context:: +first_sentence (string const& s) +{ + size_t p (s.find ('.')); + + // Add some heuristics here: check that there is a space + // (or end of text) after the period. + // + while (p != string::npos && + p + 1 <= s.size () && + s[p + 1] != ' ' && + s[p + 1] != '\n') + p = s.find ('.', p + 1); + + return p == string::npos ? s : string (s, 0, p + 1); +} + +// namespace +// + +void namespace_:: +pre (type& ns) +{ + string name (ns.name ()); + + if (!name.empty ()) + os << "namespace " << escape (name) + << "{"; +} + +void namespace_:: +post (type& ns) +{ + if (!ns.name ().empty ()) + os << "}"; +} diff --git a/cli/cli/context.hxx b/cli/cli/context.hxx new file mode 100644 index 0000000..633b8ad --- /dev/null +++ b/cli/cli/context.hxx @@ -0,0 +1,295 @@ +// file : cli/context.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_CONTEXT_HXX +#define CLI_CONTEXT_HXX + +#include <set> +#include <map> +#include <string> +#include <vector> +#include <ostream> +#include <cstddef> // std::size_t + +#include <cutl/re.hxx> +#include <cutl/shared-ptr.hxx> +#include <cutl/fs/path.hxx> + +#include <cli/options.hxx> +#include <cli/semantics.hxx> +#include <cli/traversal.hxx> + +using std::endl; + +class generation_failed {}; + +enum usage +{ + ut_none, + ut_short, + ut_long, + ut_both +}; + +enum class_doc +{ + cd_default, + cd_exclude, + cd_exclude_base, + cd_short, + cd_long +}; + +class context +{ +public: + typedef std::size_t size_t; + typedef std::string string; + + typedef cutl::fs::path path; + typedef cutl::fs::invalid_path invalid_path; + + typedef ::options options_type; + typedef ::usage usage_type; + typedef ::class_doc class_doc_type; + + // Regex. + // + typedef cutl::re::regex regex; + typedef cutl::re::regexsub regexsub; + typedef cutl::re::format regex_format; + + typedef std::vector<regexsub> regex_mapping; + +private: + struct data; + cutl::shared_ptr<data> data_; + +public: + std::ostream& os; + semantics::cli_unit& unit; + options_type const& options; + + // Documentation output type. + // + enum output_type + { + ot_plain, + ot_html, + ot_man + }; + + output_type ot; + + bool gen_modifier; + bool gen_specifier; + bool gen_parse; + bool gen_merge; + usage_type gen_usage; + + string const& inl; + string const& opt_prefix; + string const& opt_sep; + string const& cli; + + typedef std::map<string, string> reserved_name_map_type; + reserved_name_map_type const& reserved_name_map; + + typedef std::set<string> keyword_set_type; + keyword_set_type const& keyword_set; + + regex_mapping const& link_regex; + + typedef std::set<string> id_set_type; + id_set_type& id_set; + id_set_type& ref_set; + + // Map of heading ids to heading number (assigned during TOC generation) + // + typedef std::map<string, string> heading_map_type; + heading_map_type& heading_map; + + // 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_entry> toc_stack; + toc_stack& tocs; + +private: + struct data + { + string inl_; + string cli_; + keyword_set_type keyword_set_; + regex_mapping link_regex_; + id_set_type id_set_; + id_set_type ref_set_; + heading_map_type heading_map_; + unsigned short toc_; + toc_stack tocs_; + }; + +public: + // Escape C++ keywords, reserved names, and illegal characters. + // + string + escape (string const&) const; + + string + process_link_target (const string&); + + // Translate and format the documentation string. Translate converts + // the <arg>-style constructs to \i{arg}. Format converts the string + // to the output format. + // + static string + translate_arg (string const&, std::set<string>&); + + static string + translate (string const&, std::set<string> const&); + + // If para is true, start a new paragraph. + // + string + 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 (); + + // Make sure each local fragment reference has the corresponding id. Issue + // diagnostics and throw generation_failed if fails. Otherwise clear the + // id and ref sets. + // + void + verify_id_ref (); + + // 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 file contents. + // + string + 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 + // case result will contain the expansion result. + // + bool + substitute (semantics::scope&, const char* s, size_t n, string& result); + +public: + static string const& + ename (semantics::nameable& n) + { + return n.context ().get<string> ("name"); + } + + static string const& + especifier (semantics::nameable& n) + { + return n.context ().get<string> ("specifier"); + } + + static string const& + emember (semantics::nameable& n) + { + return n.context ().get<string> ("member"); + } + + static string const& + especifier_member (semantics::nameable& n) + { + return n.context ().get<string> ("specifier-member"); + } + +public: + // Return fully-qualified C++ or CLI name. + // + string + fq_name (semantics::nameable& n, bool cxx_name = true); + + // Open/close namespace. If last is false, then the last name + // component is not treated as a namespace. The open function + // also returns the last name component. + // +public: + string + ns_open (const string& name, bool last = true); + + void + ns_close (const string& name, bool last = true); + +public: + class_doc_type + class_doc (semantics::class_&); + +public: + string + first_sentence (string const&); + +public: + context (std::ostream&, + output_type, + semantics::cli_unit&, + options_type const&); + + context (context&); + +private: + context& + operator= (context const&); +}; + +// Checks if scope Y names any of X. +// +template <typename X, typename Y> +bool +has (Y& y) +{ + for (semantics::scope::names_iterator i (y.names_begin ()), + e (y.names_end ()); i != e; ++i) + if (i->named (). template is_a<X> ()) + return true; + + return false; +} + +// Standard namespace traverser. +// +struct namespace_: traversal::namespace_, context +{ + namespace_ (context& c) : context (c) {} + + virtual void + pre (type&); + + virtual void + post (type&); +}; + +#endif // CLI_CONTEXT_HXX diff --git a/cli/cli/generator.cxx b/cli/cli/generator.cxx new file mode 100644 index 0000000..df1b99e --- /dev/null +++ b/cli/cli/generator.cxx @@ -0,0 +1,584 @@ +// file : cli/generator.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <cctype> // std::toupper, std::is{alpha,upper,lower} +#include <string> +#include <fstream> +#include <iostream> + +#include <cutl/fs/auto-remove.hxx> + +#include <cutl/compiler/code-stream.hxx> +#include <cutl/compiler/cxx-indenter.hxx> + +#include <cli/header.hxx> +#include <cli/inline.hxx> +#include <cli/source.hxx> + +#include <cli/runtime-header.hxx> +#include <cli/runtime-inline.hxx> +#include <cli/runtime-source.hxx> + +#include <cli/man.hxx> +#include <cli/html.hxx> +#include <cli/txt.hxx> + +#include <cli/context.hxx> +#include <cli/generator.hxx> +#include <cli/name-processor.hxx> + +using namespace std; +using namespace cutl; + +using semantics::path; + +namespace +{ + static char const cxx_header[] = + "// -*- C++ -*-\n" + "//\n" + "// This file was generated by CLI, a command line interface\n" + "// compiler for C++.\n" + "//\n\n"; + + string + make_guard (string const& file, context& ctx) + { + string g (file); + + // Split words, e.g., "FooBar" to "Foo_Bar" and convert everything + // to upper case. + // + string r; + for (string::size_type i (0), n (g.size ()); i < n - 1; ++i) + { + char c1 (g[i]); + char c2 (g[i + 1]); + + r += toupper (c1); + + if (isalpha (c1) && isalpha (c2) && islower (c1) && isupper (c2)) + r += "_"; + } + r += toupper (g[g.size () - 1]); + + return ctx.escape (r); + } + + void + open (ifstream& ifs, string const& path) + { + ifs.open (path.c_str (), ios_base::in | ios_base::binary); + + if (!ifs.is_open ()) + { + cerr << path << ": error: unable to open in read mode" << endl; + throw generator::failed (); + } + } + + void + 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<string> const& text, string const& file) + { + for (vector<string>::const_iterator i (text.begin ()); + i != text.end (); ++i) + { + append (ctx, *i); + } + + if (!file.empty ()) + { + ifstream ifs; + open (ifs, file); + + path d (path (file).directory ()); + + // getline() will set the failbit if it failed to extract anything, + // not even the delimiter and eofbit if it reached eof before seeing + // the delimiter. + // + for (string s; getline (ifs, s); ) + append (ctx, s, &d); + } + } +} + +generator:: +generator () +{ +} + +void generator:: +generate (options& ops, semantics::cli_unit& unit, path const& p) +{ + if (ops.generate_group_scanner ()) + ops.generate_vector_scanner (true); + + try + { + path file (p.leaf ()); + string base (file.base ().string ()); + + const string& pfx (ops.output_prefix ()); + const string& sfx (ops.output_suffix ()); + + bool gen_cxx (ops.generate_cxx ()); + bool gen_man (ops.generate_man ()); + bool gen_html (ops.generate_html ()); + bool gen_txt (ops.generate_txt ()); + + if (!gen_cxx && !gen_man && !gen_html && !gen_txt) + gen_cxx = true; + + if (ops.stdout_ ()) + { + if (gen_cxx) + { + cerr << "error: --stdout cannot be used with C++ output" << endl; + throw failed (); + } + + if ((gen_man && gen_html) || + (gen_man && gen_txt) || + (gen_html && gen_txt)) + { + cerr << "error: --stdout cannot only be used with one output format" + << endl; + throw failed (); + } + } + + fs::auto_removes auto_rm; + + // C++ output. + // + if (gen_cxx) + { + bool inl (!ops.suppress_inline ()); + + string hxx_name (pfx + base + sfx + ops.hxx_suffix ()); + string ixx_name (pfx + base + sfx + ops.ixx_suffix ()); + string cxx_name (pfx + base + sfx + ops.cxx_suffix ()); + + path hxx_path (hxx_name); + path ixx_path (ixx_name); + path cxx_path (cxx_name); + + if (!ops.output_dir ().empty ()) + { + path dir (ops.output_dir ()); + + hxx_path = dir / hxx_path; + ixx_path = dir / ixx_path; + cxx_path = dir / cxx_path; + } + + // Process names. + // + { + context ctx (cerr, context::ot_plain, unit, ops); + process_names (ctx); + } + + // Check if we need to generate the runtime code. If we include + // another options file, then we assume the runtime is generated + // there. However, to reduce the number of standard headers we + // have to include in the generated header file, we will still + // need to generate some template code in the source file. + // + bool runtime (!ops.suppress_cli ()); + + if (runtime) + { + for (semantics::cli_unit::includes_iterator i (unit.includes_begin ()); + i != unit.includes_end (); + ++i) + { + if (i->is_a<semantics::cli_includes> ()) + { + runtime = false; + break; + } + } + } + + // + // + ofstream hxx (hxx_path.string ().c_str ()); + + if (!hxx.is_open ()) + { + cerr << "error: unable to open '" << hxx_path << "' in write mode" + << endl; + throw failed (); + } + + auto_rm.add (hxx_path); + + // + // + ofstream ixx; + + if (inl) + { + ixx.open (ixx_path.string ().c_str (), ios_base::out); + + if (!ixx.is_open ()) + { + cerr << "error: unable to open '" << ixx_path << "' in write mode" + << endl; + throw failed (); + } + + auto_rm.add (ixx_path); + } + + // + // + ofstream cxx (cxx_path.string ().c_str ()); + + if (!cxx.is_open ()) + { + cerr << "error: unable to open '" << cxx_path << "' in write mode" + << endl; + throw failed (); + } + + auto_rm.add (cxx_path); + + // Print headers. + // + hxx << cxx_header; + if (inl) + ixx << cxx_header; + cxx << cxx_header; + + typedef + compiler::ostream_filter<compiler::cxx_indenter, char> + cxx_filter; + + // Include settings. + // + bool br (ops.include_with_brackets ()); + string ip (ops.include_prefix ()); + string gp (ops.guard_prefix ()); + + if (!ip.empty () && ip[ip.size () - 1] != '/') + ip.append ("/"); + + if (!gp.empty () && gp[gp.size () - 1] != '_') + gp.append ("_"); + + // HXX + // + { + context ctx (hxx, context::ot_plain, unit, ops); + + string guard (make_guard (gp + hxx_name, ctx)); + + hxx << "#ifndef " << guard << endl + << "#define " << guard << endl + << endl; + + // Copy prologue. + // + hxx << "// Begin prologue." << endl + << "//" << endl; + append (ctx, ops.hxx_prologue (), ops.hxx_prologue_file ()); + hxx << "//" << endl + << "// End prologue." << endl + << endl; + + { + // We don't want to indent prologues/epilogues. + // + cxx_filter filt (ctx.os); + + if (runtime) + generate_runtime_header (ctx); + + generate_header (ctx); + } + + if (inl) + { + hxx << "#include " << (br ? '<' : '"') << ip << ixx_name << + (br ? '>' : '"') << endl + << endl; + } + + // Copy epilogue. + // + hxx << "// Begin epilogue." << endl + << "//" << endl; + append (ctx, ops.hxx_epilogue (), ops.hxx_epilogue_file ()); + hxx << "//" << endl + << "// End epilogue." << endl + << endl; + + hxx << "#endif // " << guard << endl; + } + + // IXX + // + if (inl) + { + context ctx (ixx, context::ot_plain, unit, ops); + + // Copy prologue. + // + ixx << "// Begin prologue." << endl + << "//" << endl; + append (ctx, ops.ixx_prologue (), ops.ixx_prologue_file ()); + ixx << "//" << endl + << "// End prologue." << endl + << endl; + + { + // We don't want to indent prologues/epilogues. + // + cxx_filter filt (ctx.os); + + if (runtime) + generate_runtime_inline (ctx); + + generate_inline (ctx); + } + + // Copy epilogue. + // + ixx << "// Begin epilogue." << endl + << "//" << endl; + append (ctx, ops.ixx_epilogue (), ops.ixx_epilogue_file ()); + ixx << "//" << endl + << "// End epilogue." << endl; + } + + // CXX + // + { + context ctx (cxx, context::ot_plain, unit, ops); + + // Copy prologue. + // + cxx << "// Begin prologue." << endl + << "//" << endl; + append (ctx, ops.cxx_prologue (), ops.cxx_prologue_file ()); + cxx << "//" << endl + << "// End prologue." << endl + << endl; + + cxx << "#include " << (br ? '<' : '"') << ip << hxx_name << + (br ? '>' : '"') << endl + << endl; + + { + // We don't want to indent prologues/epilogues. + // + cxx_filter filt (ctx.os); + + if (runtime && !inl) + generate_runtime_inline (ctx); + + if (!ops.suppress_cli ()) + generate_runtime_source (ctx, runtime); + + if (!inl) + generate_inline (ctx); + + generate_source (ctx); + } + + // Copy epilogue. + // + cxx << "// Begin epilogue." << endl + << "//" << endl; + append (ctx, ops.cxx_epilogue (), ops.cxx_epilogue_file ()); + cxx << "//" << endl + << "// End epilogue." << endl + << endl; + } + } + + // man output + // + if (gen_man) + { + ofstream man; + + if (!ops.stdout_ ()) + { + path man_path (pfx + base + sfx + ops.man_suffix ()); + + if (!ops.output_dir ().empty ()) + man_path = path (ops.output_dir ()) / man_path; + + man.open (man_path.string ().c_str ()); + + if (!man.is_open ()) + { + cerr << "error: unable to open '" << man_path << "' in write mode" + << endl; + throw failed (); + } + + auto_rm.add (man_path); + } + + // The explicit cast helps VC++ 8.0 overcome its issues. + // + ostream& os (ops.stdout_ () ? cout : static_cast<ostream&> (man)); + context ctx (os, context::ot_man, unit, ops); + + 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 ()); + + if (ctx.toc) + { + assert (first); // Second run should end in non-TOC mode. + ctx.toc++; // TOC phase after restart. + } + } + + ctx.verify_id_ref (); + } + + // HTML output + // + if (gen_html) + { + ofstream html; + + if (!ops.stdout_ ()) + { + // May have to update link derivation in format_line() if changing + // this. + // + path html_path (pfx + base + sfx + ops.html_suffix ()); + + if (!ops.output_dir ().empty ()) + html_path = path (ops.output_dir ()) / html_path; + + html.open (html_path.string ().c_str ()); + + if (!html.is_open ()) + { + cerr << "error: unable to open '" << html_path << "' in write mode" + << endl; + throw failed (); + } + + auto_rm.add (html_path); + } + + // The explicit cast helps VC++ 8.0 overcome its issues. + // + ostream& os (ops.stdout_ () ? cout : static_cast<ostream&> (html)); + context ctx (os, context::ot_html, unit, ops); + + 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 ()); + + if (ctx.toc) + { + assert (first); // Second run should end in non-TOC mode. + ctx.toc++; // TOC phase after restart. + } + } + + ctx.verify_id_ref (); + } + + // txt output + // + if (gen_txt) + { + ofstream txt; + + if (!ops.stdout_ ()) + { + path txt_path (pfx + base + sfx + ops.txt_suffix ()); + + if (!ops.output_dir ().empty ()) + txt_path = path (ops.output_dir ()) / txt_path; + + txt.open (txt_path.string ().c_str ()); + + if (!txt.is_open ()) + { + cerr << "error: unable to open '" << txt_path << "' in write mode" + << endl; + throw failed (); + } + + auto_rm.add (txt_path); + } + + // The explicit cast helps VC++ 8.0 overcome its issues. + // + ostream& os (ops.stdout_ () ? cout : static_cast<ostream&> (txt)); + context ctx (os, context::ot_plain, unit, ops); + + 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 ()); + + if (ctx.toc) + { + assert (first); // Second run should end in non-TOC mode. + ctx.toc++; // TOC phase after restart. + } + } + + ctx.verify_id_ref (); + } + + auto_rm.cancel (); + } + catch (const generation_failed&) + { + // Code generation failed. Diagnostics has already been issued. + // + throw failed (); + } + catch (semantics::invalid_path const& e) + { + cerr << "error: '" << e.path () << "' is not a valid filesystem path" + << endl; + throw failed (); + } + catch (fs::error const&) + { + // Auto-removal of generated files failed. Ignore it. + // + throw failed (); + } +} diff --git a/cli/cli/generator.hxx b/cli/cli/generator.hxx new file mode 100644 index 0000000..f567528 --- /dev/null +++ b/cli/cli/generator.hxx @@ -0,0 +1,28 @@ +// file : cli/generator.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_GENERATOR_HXX +#define CLI_GENERATOR_HXX + +#include <cli/options.hxx> +#include <cli/semantics/unit.hxx> + +class generator +{ +public: + generator (); + + class failed {}; + + void + generate (options&, semantics::cli_unit&, semantics::path const&); + +private: + generator (generator const&); + + generator& + operator= (generator const&); +}; + +#endif // CLI_GENERATOR_HXX diff --git a/cli/cli/header.cxx b/cli/cli/header.cxx new file mode 100644 index 0000000..a2a3ccd --- /dev/null +++ b/cli/cli/header.cxx @@ -0,0 +1,383 @@ +// file : cli/header.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <cli/header.hxx> + +using namespace std; + +namespace +{ + // + // + struct option: traversal::option, context + { + option (context& c) : context (c) {} + + virtual void + traverse (type& o) + { + string name (ename (o)); + string type (o.type ().name ()); + + os << "const " << type << "&" << endl + << name << " () const;" + << endl; + + if (gen_modifier) + { + os << type << "&" << endl + << name << " ();" + << endl; + + os << "void" << endl + << name << " (const " << type << "&);" + << endl; + } + + if (gen_specifier && type != "bool") + { + string spec (especifier (o)); + + os << "bool" << endl + << spec << " () const;" + << endl; + + if (gen_modifier) + os << "void" << endl + << spec << " (bool);" + << endl; + } + } + }; + + // + // + struct option_data: traversal::option, context + { + option_data (context& c) : context (c) {} + + virtual void + traverse (type& o) + { + string member (emember (o)); + string type (o.type ().name ()); + + os << type << " " << member << ";"; + + if (gen_specifier && type != "bool") + os << "bool " << especifier_member (o) << ";"; + } + }; + + // + // + struct base: traversal::class_, context + { + base (context& c): context (c), first_ (true) {} + + virtual void + traverse (type& c) + { + if (first_) + { + os << ": "; + first_ = false; + } + else + os << "," << endl + << " "; + + os << "public " << fq_name (c); + } + + private: + bool first_; + }; + + // + // + struct class_: traversal::class_, context + { + class_ (context& c) + : context (c), + option_ (c), + option_data_ (c) + { + names_option_ >> option_; + names_option_data_ >> option_data_; + } + + virtual void + traverse (type& c) + { + bool abst (c.abstract ()); + string name (escape (c.name ())); + string um (cli + "::unknown_mode"); + + os << "class " << name; + + { + base b (*this); + traversal::inherits i (b); + inherits (c, i); + } + + os << "{" + << "public:" << endl; + + // c-tors + // + if (!abst) + { + os << name << " ();" + << endl; + + // Are we generating parsing constructors or parse() functions? + // + string n; + if (gen_parse) + { + os << "// Return true if anything has been parsed." << endl + << "//" << endl; + + n = string ("bool\n") + (name != "parse" ? "parse" : "parse_"); + } + else + n = name; + + os << n << " (int& argc," << endl + << "char** argv," << endl + << "bool erase = false," << endl + << um << " option = " << um << "::fail," << endl + << um << " argument = " << um << "::stop);" + << endl; + + os << n << " (int start," << endl + << "int& argc," << endl + << "char** argv," << endl + << "bool erase = false," << endl + << um << " option = " << um << "::fail," << endl + << um << " argument = " << um << "::stop);" + << endl; + + os << n << " (int& argc," << endl + << "char** argv," << endl + << "int& end," << endl + << "bool erase = false," << endl + << um << " option = " << um << "::fail," << endl + << um << " argument = " << um << "::stop);" + << endl; + + os << n << " (int start," << endl + << "int& argc," << endl + << "char** argv," << endl + << "int& end," << endl + << "bool erase = false," << endl + << um << " option = " << um << "::fail," << endl + << um << " argument = " << um << "::stop);" + << endl; + + os << n << " (" << cli << "::scanner&," << endl + << um << " option = " << um << "::fail," << endl + << um << " argument = " << um << "::stop);" + << endl; + } + + + // Note that we are generating public merge() function even for abstract + // classes; theoretically, one may want to merge options only starting + // form a specific point in the inheritance hierarchy (e.g., only common + // options or some such). + // + if (gen_merge) + os << "// Merge options from the specified instance appending/overriding" << endl + << "// them as if they appeared after options in this instance." << endl + << "//" << endl + << "void" << endl + << "merge (const " << name << "&);" + << endl; + + // + // + os << "// Option accessors" << (gen_modifier ? " and modifiers." : ".") << endl + << "//" << endl; + + names (c, names_option_); + + // Usage. + // + if (gen_usage != ut_none) + { + string up (cli + "::usage_para"); + string const& ost (options.ostream_type ()); + + os << "// Print usage information." << endl + << "//" << endl; + + os << "static " << up << endl + << "print_usage (" << ost << "&," << endl + << up << " = " << up << "::none);" + << endl; + + if (gen_usage == ut_both) + os << "static " << up << endl + << "print_long_usage (" << ost << "&," << endl + << up << " = " << up << "::none);" + << endl; + } + + // Description. + // + if (options.generate_description ()) + { + os << "// Option description." << endl + << "//" << endl + << "static const " << cli << "::options&" << endl + << "description ();" + << endl; + } + + os << "// Implementation details." << endl + << "//" << endl + << "protected:" << endl; + + // default c-tor (abstract) + // + if (abst) + os << name << " ();" + << endl; + + // fill () + // + if (options.generate_description ()) + os << "friend struct _cli_" + name + "_desc_type;" + << endl + << "static void" << endl + << "fill (" << cli << "::options&);" + << endl; + + // _parse () + // + os << "bool" << endl + << "_parse (const char*, " << cli << "::scanner&);" + << endl; + + // _parse () + // + if (!abst) + os << "private:" << endl + << "bool" << endl + << "_parse (" << cli << "::scanner&," << endl + << um << " option," << endl + << um << " argument);" + << endl; + + // Data members. + // + os << "public:" << endl; //@@ tmp + + names (c, names_option_data_); + + os << "};"; + } + + private: + option option_; + traversal::names names_option_; + + option_data option_data_; + traversal::names names_option_data_; + }; + + // + // + struct includes_: traversal::cxx_includes, + traversal::cli_includes, + context + { + includes_ (context& c) : context (c) {} + + virtual void + traverse (semantics::cxx_includes& i) + { + generate (i.kind (), i.file ().string ()); + } + + virtual void + traverse (semantics::cli_includes& i) + { + generate (i.kind (), + (options.output_prefix () + + i.file ().base ().string () + + options.output_suffix () + + options.hxx_suffix ())); + } + + void + generate (semantics::includes::kind_type k, string const& f) + { + char b, e; + if (k == semantics::includes::quote) + b = e = '"'; + else + { + b = '<'; + e = '>'; + } + + os << "#include " << b << f << e << endl + << endl; + } + }; +} + +void +generate_header (context& ctx) +{ + ostream& os (ctx.os); + + traversal::cli_unit unit; + includes_ includes (ctx); + traversal::names unit_names; + namespace_ ns (ctx); + class_ cl (ctx); + + unit >> includes; + unit >> unit_names >> ns; + unit_names >> cl; + + traversal::names ns_names; + + ns >> ns_names >> ns; + ns_names >> cl; + + unit.dispatch (ctx.unit); + + // Entire page usage. + // + if (ctx.gen_usage != ut_none && ctx.options.page_usage_specified ()) + { + os << "// Print page usage information." << endl + << "//" << endl; + + const string& qn (ctx.options.page_usage ()); + string n (ctx.escape (ctx.substitute (ctx.ns_open (qn, false)))); + + string up (ctx.cli + "::usage_para"); + string const& ost (ctx.options.ostream_type ()); + + os << up << endl + << n << "usage (" << ost << "&," << endl + << up << " = " << up << "::none);" + << endl; + + if (ctx.gen_usage == ut_both) + os << up << endl + << n << "long_usage (" << ost << "&," << endl + << up << " = " << up << "::none);" + << endl; + + ctx.ns_close (qn, false); + } +} diff --git a/cli/cli/header.hxx b/cli/cli/header.hxx new file mode 100644 index 0000000..e6e68ee --- /dev/null +++ b/cli/cli/header.hxx @@ -0,0 +1,13 @@ +// file : cli/header.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_HEADER_HXX +#define CLI_HEADER_HXX + +#include <cli/context.hxx> + +void +generate_header (context&); + +#endif // CLI_HEADER_HXX diff --git a/cli/cli/html.cxx b/cli/cli/html.cxx new file mode 100644 index 0000000..b374b91 --- /dev/null +++ b/cli/cli/html.cxx @@ -0,0 +1,348 @@ +// file : cli/html.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <vector> +#include <iostream> + +#include <cli/html.hxx> + +using namespace std; + +namespace +{ + static string + escape_html (string const& s) + { + string r; + r.reserve (s.size ()); + + for (size_t i (0), n (s.size ()); i < n; ++i) + { + switch (s[i]) + { + case '<': + { + r += "<"; + break; + } + case '&': + { + r += "&"; + break; + } + default: + { + r += s[i]; + break; + } + } + } + + return r; + } + + static void + wrap_lines (ostream& os, const string& d, size_t indent) + { + size_t lim (78 - indent); + string ind (indent, ' '); + + bool nl (true); // True if last written to os character is a newline. + + size_t b (0), e (0), i (0); + for (size_t n (d.size ()); i < n; ++i) + { + // First handle <pre>. + // + if (d.compare (i, 5, "<pre>") == 0) + { + // Write what might have already accumulated. + // + if (b != i) + { + if (nl) + os << ind; + + os << string (d, b, i - b); + nl = false; + } + + // Output everything until (and including) closing </pre> as is. + // + e = d.find ("</pre>", i + 5); + assert (e != string::npos); + e += 6; // Now points past '>'. + + if (nl) + os << ind; + + os << string (d, i, e - i); + + b = e; + i = e - 1; // For ++i in loop header. + nl = false; + continue; + } + + if (d[i] == ' ' || d[i] == '\n') + e = i; + + if (d[i] == '\n' || (i - b >= lim && e != b)) + { + if (nl && b != e) + os << ind; + + os << string (d, b, e - b) << endl; + + b = e = e + 1; + nl = true; + } + } + + // Write the last line. + // + if (b != i) + { + if (nl) + os << ind; + + os << string (d, b, i - b); + } + } + + struct doc: traversal::doc, context + { + doc (context& c, class_doc_type cd, bool& l) + : context (c), cd_ (cd), list_ (l) {} + + virtual void + traverse (type& ds) + { + if (ds.name ().compare (0, 3, "doc") != 0) // Ignore doc variables. + return; + + // n = 1 - common doc string + // n = 2 - arg string, common doc string + // n > 2 - arg string, short string, long string + // + size_t n (ds.size ()); + const string& d ( + n == 1 + ? (cd_ == cd_short ? first_sentence (ds[0]) : ds[0]) + : (n == 2 + ? (cd_ == cd_short ? first_sentence (ds[1]) : ds[1]) + : ds[cd_ == cd_short ? 1 : 2])); + + std::set<string> arg_set; + if (n > 1) + translate_arg (ds[0], arg_set); + + 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; + + if (list_) + { + os << " </dl>" << endl + << endl; + list_ = false; + } + + 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: + class_doc_type cd_; + bool& list_; // True if we are currently in <dl>. + }; + + struct option: traversal::option, context + { + option (context& c, class_doc_type cd, bool& l) + : context (c), cd_ (cd), list_ (l) {} + + virtual void + traverse (type& o) + { + using semantics::names; + + semantics::doc_strings const& doc (o.doc ()); + + if (options.suppress_undocumented () && doc.empty ()) + return; + + if (toc) + return; // No option documentation in the TOC mode. + + if (!list_) + { + os << " <dl class=\"options\">" << endl; + list_ = true; + } + else + os << endl; // Separate from the previous <dl>. + + names& n (o.named ()); + + os << " <dt><code><b>"; + + for (names::name_iterator i (n.name_begin ()); i != n.name_end (); ++i) + { + if (i != n.name_begin ()) + os << "</b></code>|<code><b>"; + + os << escape_html (*i); + } + + os << "</b></code>"; + + string type (o.type ().name ()); + + std::set<string> arg_set; + + if (type != "bool" || doc.size () >= 3) + { + string s ( + translate_arg ( + doc.size () > 0 ? doc[0] : string ("<arg>"), arg_set)); + + os << ' ' << format (o.scope (), escape_html (s), false); + } + + os << "</dt>" << endl; + + string d; + if (type == "bool" && doc.size () < 3) + { + if (doc.size () > 1) + d = doc[cd_ == cd_short ? 0 : 1]; + else if (doc.size () > 0) + d = (cd_ == cd_short ? first_sentence (doc[0]) : doc[0]); + } + else + { + if (doc.size () > 2) + d = doc[cd_ == cd_short ? 1 : 2]; + else if (doc.size () > 1) + d = (cd_ == cd_short ? first_sentence (doc[1]) : doc[1]); + } + + // Format the documentation string. + // + d = format (o.scope (), escape_html (translate (d, arg_set)), false); + + wrap_lines (os, "<dd>" + d + "</dd>", 4); + os << endl; + } + + private: + class_doc_type cd_; + bool& list_; // True if we are currently in <dl>. + }; + + // + // + struct class_: traversal::class_, context + { + class_ (context& c, bool& l): context (c), list_ (l), base_ (false) + { + *this >> inherits_ >> *this; + } + + virtual void + traverse (type& c) + { + class_doc_type cd (class_doc (c)); + + if (cd == cd_exclude || (base_ && cd == cd_exclude_base)) + return; + + if (!options.exclude_base () && !options.include_base_last ()) + { + bool ob (base_); + base_ = true; + inherits (c); + base_ = ob; + } + + doc dc (*this, cd, list_); + option op (*this, cd, list_); + traversal::names n; + n >> dc; + n >> op; + names (c, n); + + if (!options.exclude_base () && options.include_base_last ()) + { + bool ob (base_); + base_ = true; + inherits (c); + base_ = ob; + } + } + + private: + bool& list_; + bool base_; + traversal::inherits inherits_; + }; +} + +void +generate_html (context& ctx) +{ + bool list (false); + + traversal::cli_unit unit; + traversal::names unit_names; + traversal::namespace_ ns; + doc dc (ctx, cd_default, list); + class_ cl (ctx, list); + unit >> unit_names; + unit_names >> dc; + unit_names >> ns; + unit_names >> cl; + + traversal::names ns_names; + ns >> ns_names; + ns_names >> dc; + ns_names >> ns; + ns_names >> cl; + + if (ctx.options.class_ ().empty ()) + unit.dispatch (ctx.unit); + else + { + for (vector<string>::const_iterator i (ctx.options.class_ ().begin ()); + i != ctx.options.class_ ().end (); ++i) + { + string n (*i); + + // Strip leading :: if present. + // + if (n.size () > 2 && n[0] == ':' && n[1] == ':') + n = string (n, 2, string::npos); + + if (semantics::class_* c = ctx.unit.lookup<semantics::class_> ("", n)) + cl.traverse (*c); + else + { + cerr << "error: class '" << *i << "' not found" << endl; + throw generation_failed (); + } + } + } + + if (list) + ctx.os << " </dl>" << endl + << endl; +} diff --git a/cli/cli/html.hxx b/cli/cli/html.hxx new file mode 100644 index 0000000..4ba5a41 --- /dev/null +++ b/cli/cli/html.hxx @@ -0,0 +1,13 @@ +// file : cli/html.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_HTML_HXX +#define CLI_HTML_HXX + +#include <cli/context.hxx> + +void +generate_html (context&); + +#endif // CLI_HTML_HXX diff --git a/cli/cli/inline.cxx b/cli/cli/inline.cxx new file mode 100644 index 0000000..05b83db --- /dev/null +++ b/cli/cli/inline.cxx @@ -0,0 +1,108 @@ +// file : cli/inline.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <cli/inline.hxx> + +namespace +{ + // + // + struct option: traversal::option, context + { + option (context& c) : context (c) {} + + virtual void + traverse (type& o) + { + string name (ename (o)); + string type (o.type ().name ()); + string scope (escape (o.scope ().name ())); + + os << inl << "const " << type << "& " << scope << "::" << endl + << name << " () const" + << "{" + << "return this->" << emember (o) << ";" + << "}"; + + if (gen_modifier) + { + os << inl << type << "& " << scope << "::" << endl + << name << " ()" + << "{" + << "return this->" << emember (o) << ";" + << "}"; + + os << inl << "void " << scope << "::" << endl + << name << "(const " << type << "& x)" + << "{" + << "this->" << emember (o) << " = x;" + << "}"; + } + + if (gen_specifier && type != "bool") + { + string spec (especifier (o)); + + os << inl << "bool " << scope << "::" << endl + << spec << " () const" + << "{" + << "return this->" << especifier_member (o) << ";" + << "}"; + + if (gen_modifier) + os << inl << "void " << scope << "::" << endl + << spec << "(bool x)" + << "{" + << "this->" << especifier_member (o) << " = x;" + << "}"; + } + } + }; + + // + // + struct class_: traversal::class_, context + { + class_ (context& c) + : context (c), option_ (c) + { + names_option_ >> option_; + } + + virtual void + traverse (type& c) + { + string name (escape (c.name ())); + + os << "// " << name << endl + << "//" << endl + << endl; + + names (c, names_option_); + } + + private: + option option_; + traversal::names names_option_; + }; +} + +void +generate_inline (context& ctx) +{ + traversal::cli_unit unit; + traversal::names unit_names; + namespace_ ns (ctx); + class_ cl (ctx); + + unit >> unit_names >> ns; + unit_names >> cl; + + traversal::names ns_names; + + ns >> ns_names >> ns; + ns_names >> cl; + + unit.dispatch (ctx.unit); +} diff --git a/cli/cli/inline.hxx b/cli/cli/inline.hxx new file mode 100644 index 0000000..38e7768 --- /dev/null +++ b/cli/cli/inline.hxx @@ -0,0 +1,13 @@ +// file : cli/inline.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_INLINE_HXX +#define CLI_INLINE_HXX + +#include <cli/context.hxx> + +void +generate_inline (context&); + +#endif // CLI_INLINE_HXX diff --git a/cli/cli/lexer.cxx b/cli/cli/lexer.cxx new file mode 100644 index 0000000..573c76b --- /dev/null +++ b/cli/cli/lexer.cxx @@ -0,0 +1,604 @@ +// file : cli/lexer.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <iostream> + +#include <cli/lexer.hxx> + +using namespace std; + +lexer:: +lexer (istream& is, string const& id) + : loc_ ("C"), + is_ (is), + id_ (id), + l_ (1), + c_(1), + eos_ (false), + include_ (false), + valid_ (true), + buf_ (0, 0, 0), + unget_ (false) +{ + keyword_map_["source"] = token::k_source; + keyword_map_["include"] = token::k_include; + keyword_map_["namespace"] = token::k_namespace; + keyword_map_["class"] = token::k_class; + keyword_map_["signed"] = token::k_signed; + keyword_map_["unsigned"] = token::k_unsigned; + keyword_map_["bool"] = token::k_bool; + keyword_map_["char"] = token::k_char; + keyword_map_["wchar_t"] = token::k_wchar; + keyword_map_["short"] = token::k_short; + keyword_map_["int"] = token::k_int; + keyword_map_["long"] = token::k_long; + keyword_map_["float"] = token::k_float; + keyword_map_["double"] = token::k_double; +} + +lexer::xchar lexer:: +peek () +{ + if (unget_) + return buf_; + else + { + if (eos_) + return xchar (xchar::traits_type::eof (), l_, c_); + else + { + xchar::int_type i (is_.peek ()); + + if (i == xchar::traits_type::eof ()) + eos_ = true; + + return xchar (i, l_, c_); + } + } +} + +lexer::xchar lexer:: +get () +{ + if (unget_) + { + unget_ = false; + return buf_; + } + else + { + // When is_.get () returns eof, the failbit is also set (stupid, + // isn't?) which may trigger an exception. To work around this + // we will call peek() first and only call get() if it is not + // eof. But we can only call peek() on eof once; any subsequent + // calls will spoil the failbit (even more stupid). + // + xchar c (peek ()); + + if (!is_eos (c)) + { + is_.get (); + + if (c == '\n') + { + l_++; + c_ = 1; + } + else + c_++; + } + + return c; + } +} + +void lexer:: +unget (xchar c) +{ + // Because iostream::unget cannot work once eos is reached, + // we have to provide our own implementation. + // + buf_ = c; + unget_ = true; +} + +token lexer:: +next () +{ + while (true) // Recovery loop. + { + bool include (include_); + include_ = false; + + skip_spaces (); + + xchar c (get ()); + + if (is_eos (c)) + return token (c.line (), c.column ()); + + try + { + switch (c) + { + case '\'': + { + return char_literal (c); + } + case '\"': + { + if (include) + return path_literal (c); + else + return string_literal (c); + } + case '<': + { + if (include) + return path_literal (c); + else + return template_expression (c); + } + case ';': + { + return token (token::p_semi, c.line (), c.column ()); + } + case ',': + { + return token (token::p_comma, c.line (), c.column ()); + } + case ':': + { + if (peek () == ':') + { + get (); + return token (token::p_dcolon, c.line (), c.column ()); + } + + return token (token::p_colon, c.line (), c.column ()); + } + case '{': + { + return token (token::p_lcbrace, c.line (), c.column ()); + } + case '}': + { + return token (token::p_rcbrace, c.line (), c.column ()); + } + case '(': + { + return call_expression (c); + } + case '=': + { + return token (token::p_eq, c.line (), c.column ()); + } + case '|': + { + return token (token::p_or, c.line (), c.column ()); + } + case '-': + { + // This can be a beginning of an identifier or a an integer + // literal. Figure out which one it is. + // + xchar p (peek ()); + + if (is_dec_digit (p)) + return int_literal (get (), true, c.line (), c.column ()); + else if (is_space (p)) + { + skip_spaces (); + p = peek (); + + if (is_dec_digit (p)) + return int_literal (get (), true, c.line (), c.column ()); + + // Stray '-'. + // + cerr << id_ << ':' << c.line () << ':' << c.column () + << ": error: unexpected character '-'" << endl; + throw invalid_input (); + } + + break; + } + } + + if (is_alpha (c) || c == '_' || c == '-' || c == '/') + { + return identifier (c); + } + + if (is_dec_digit (c)) + { + return int_literal (c); + } + + cerr << id_ << ':' << c.line () << ':' << c.column () + << ": error: unexpected character '" << c << "'" << endl; + throw invalid_input (); + } + catch (invalid_input const&) + { + valid_ = false; + } + + // Try to recover. + // + do + { + c = get (); + + if (is_eos (c)) + return token (c.line (), c.column ()); + } while (c != ';'); + } +} + +void lexer:: +skip_spaces () +{ + for (xchar c (peek ());; c = peek ()) + { + if (is_eos (c)) + break; + + if (c == '/') + { + c = get (); + xchar p (peek ()); + + if (p == '/') + { + get (); + + // C++ comment. Read until newline or eos. + // + for (c = get (); !is_eos (c) && c != '\n'; c = get ()) ; + continue; + } + else if (p == '*') + { + get (); + + // C comment. + // + for (c = get ();; c = get ()) + { + if (is_eos (c)) + { + cerr << id_ << ':' << c.line () << ':' << c.column () + << ": error: end of stream reached while reading " + << "C-style comment" << endl; + throw invalid_input (); + } + + if (c == '*') + { + c = peek (); + if (c == '/') + { + get (); + break; + } + } + } + continue; + } + else + { + unget (c); + break; + } + } + + if (!is_space (c)) + break; + + get (); + } +} + +token lexer:: +identifier (xchar c) +{ + size_t ln (c.line ()), cl (c.column ()); + string lexeme; + lexeme += c; + + bool check (c == '-' || c == '/'); + + for (c = peek (); + !is_eos (c) && (is_alnum (c) || c == '_' || c == '-'); + c = peek ()) + { + get (); + lexeme += c; + } + + // Check for invalid identifiers. + // + if (check) + { + size_t i (1); + + for (; i < lexeme.size (); ++i) + if (is_alnum (lexeme[i]) || lexeme[i] == '_') + break; + + if (i == lexeme.size ()) + { + cerr << id_ << ':' << c.line () << ':' << c.column () << ": error: " + << "invalid character sequence '" << lexeme << "'" << endl; + throw invalid_input (); + } + } + + keyword_map::const_iterator i (keyword_map_.find (lexeme)); + + if (i != keyword_map_.end ()) + { + if (i->second == token::k_include || i->second == token::k_source) + include_ = true; + + return token (i->second, ln, cl); + } + + if (lexeme == "true" || lexeme == "false") + return token (token::t_bool_lit, lexeme, ln, cl); + + return token (token::t_identifier, lexeme, ln, cl); +} + +token lexer:: +int_literal (xchar c, bool neg, size_t ml, size_t mc) +{ + size_t ln (neg ? ml : c.line ()), cl (neg ? mc : c.column ()); + string lexeme; + + if (neg) + lexeme += '-'; + + lexeme += c; + + for (c = peek (); !is_eos (c) && is_dec_digit (c); c = peek ()) + { + get (); + lexeme += c; + } + + return token (token::t_int_lit, lexeme, ln, cl); +} + +token lexer:: +char_literal (xchar c) +{ + size_t ln (c.line ()), cl (c.column ()); + string lexeme; + lexeme += c; + + char p (c); + + while (true) + { + c = get (); + + if (is_eos (c)) + { + cerr << id_ << ':' << c.line () << ':' << c.column () << ": error: " + << "end of stream reached while reading character literal" << endl; + throw invalid_input (); + } + + lexeme += c; + + if (c == '\'' && p != '\\') + break; + + // We need to keep track of \\ escapings so we don't confuse + // them with \', as in '\\'. + // + if (c == '\\' && p == '\\') + p = '\0'; + else + p = c; + } + + return token (token::t_char_lit, lexeme, ln, cl); +} + +token lexer:: +string_literal (xchar c) +{ + size_t ln (c.line ()), cl (c.column ()); + string lexeme; + lexeme += c; + + while (true) + { + lexeme += string_literal_trailer (); + + // Check if there are more strings. + // + skip_spaces (); + + c = peek (); + + if (is_eos (c) || c != '"') + break; + + get (); + lexeme += "\""; + } + + return token (token::t_string_lit, lexeme, ln, cl); +} + +string lexer:: +string_literal_trailer () +{ + string r; + char p ('\0'); + + while (true) + { + xchar c = get (); + + if (is_eos (c)) + { + cerr << id_ << ':' << c.line () << ':' << c.column () << ": error: " + << "end of stream reached while reading string literal" << endl; + throw invalid_input (); + } + + r += c; + + if (c == '"' && p != '\\') + break; + + // We need to keep track of \\ escapings so we don't confuse + // them with \", as in "\\". + // + if (c == '\\' && p == '\\') + p = '\0'; + else + p = c; + } + + return r; +} + +token lexer:: +path_literal (xchar c) +{ + size_t ln (c.line ()), cl (c.column ()); + string lexeme; + lexeme += c; + + char end (c == '<' ? '>' : '"'); + + while (true) + { + c = get (); + + if (is_eos (c)) + { + cerr << id_ << ':' << c.line () << ':' << c.column () << ": error: " + << "end of stream reached while reading path literal" << endl; + throw invalid_input (); + } + + lexeme += c; + + if (c == end) + break; + } + + token::token_type tt; + + if (lexeme.compare (1, 4, "c++:") == 0) + { + tt = token::t_cxx_path_lit; + lexeme = lexeme[0] + string (lexeme, 5, string::npos); + } + else if (lexeme.compare (1, 4, "cli:") == 0) + { + tt = token::t_cli_path_lit; + lexeme = lexeme[0] + string (lexeme, 5, string::npos); + } + else + { + // See if the path ends with .cli. If not, then we assume this is + // a C++ inclusion. + // + size_t n (lexeme.size ()); + + if (n > 5 && lexeme.compare (n - 5, 4, ".cli") == 0) + tt = token::t_cli_path_lit; + else + tt = token::t_cxx_path_lit; + } + + return token (tt, lexeme, ln, cl); +} + +token lexer:: +call_expression (xchar c) +{ + size_t ln (c.line ()), cl (c.column ()); + string lexeme; + lexeme += c; + size_t balance (1); + + while (balance != 0) + { + c = get (); + + if (is_eos (c)) + { + cerr << id_ << ':' << c.line () << ':' << c.column () << ": error: " + << "end of stream reached while reading call expression" << endl; + throw invalid_input (); + } + + lexeme += c; + + switch (c) + { + case '(': + { + balance++; + break; + } + case ')': + { + balance--; + break; + } + } + } + + return token (token::t_call_expr, lexeme, ln, cl); +} + +token lexer:: +template_expression (xchar c) +{ + size_t ln (c.line ()), cl (c.column ()); + string lexeme; + lexeme += c; + size_t balance (1); + + while (balance != 0) + { + c = get (); + + if (is_eos (c)) + { + cerr << id_ << ':' << c.line () << ':' << c.column () << ": error: " + << "end of stream reached while reading template expression" + << endl; + throw invalid_input (); + } + + lexeme += c; + + switch (c) + { + case '<': + { + balance++; + break; + } + case '>': + { + balance--; + break; + } + } + } + + return token (token::t_template_expr, lexeme, ln, cl); +} diff --git a/cli/cli/lexer.hxx b/cli/cli/lexer.hxx new file mode 100644 index 0000000..bd7b0c9 --- /dev/null +++ b/cli/cli/lexer.hxx @@ -0,0 +1,142 @@ +// file : cli/lexer.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_LEXER_HXX +#define CLI_LEXER_HXX + +#include <map> +#include <string> +#include <locale> +#include <cstddef> // std::size_t +#include <istream> + +#include <cli/token.hxx> + +class lexer +{ +public: + lexer (std::istream& is, std::string const& id); + + token + next (); + + bool + valid () const; + +protected: + class xchar + { + public: + typedef std::char_traits<char> traits_type; + typedef traits_type::int_type int_type; + typedef traits_type::char_type char_type; + + xchar (int_type v, std::size_t l, std::size_t c); + + operator char_type () const; + + int_type + value () const; + + std::size_t + line () const; + + std::size_t + column () const; + + private: + int_type v_; + std::size_t l_; + std::size_t c_; + }; + + xchar + peek (); + + xchar + get (); + + void + unget (xchar); + +protected: + class invalid_input {}; + + void + skip_spaces (); + + token + identifier (xchar); + + token + int_literal (xchar, + bool neg = false, + std::size_t ml = 0, + std::size_t mc = 0); + + token + char_literal (xchar); + + token + string_literal (xchar); + + std::string + string_literal_trailer (); + + token + path_literal (xchar); + + token + call_expression (xchar); + + token + template_expression (xchar); + +protected: + bool + is_alpha (char c) const; + + bool + is_oct_digit (char c) const; + + bool + is_dec_digit (char c) const; + + bool + is_hex_digit (char c) const; + + bool + is_alnum (char c) const; + + bool + is_space (char c) const; + + bool + is_eos (xchar const& c) const; + + char + to_upper (char c) const; + +private: + typedef std::map<std::string, token::keyword_type> keyword_map; + + std::locale loc_; + std::istream& is_; + std::string id_; + std::size_t l_; + std::size_t c_; + + keyword_map keyword_map_; + + bool eos_; + bool include_; // Literal in include or source. + bool valid_; + + xchar buf_; + bool unget_; +}; + +#include <cli/lexer.ixx> + +#endif // CLI_LEXER_HXX diff --git a/cli/cli/lexer.ixx b/cli/cli/lexer.ixx new file mode 100644 index 0000000..1c4c42e --- /dev/null +++ b/cli/cli/lexer.ixx @@ -0,0 +1,91 @@ +// file : cli/lexer.ixx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +// lexer::xchar +// +inline lexer::xchar:: +xchar (int_type v, std::size_t l, std::size_t c) + : v_ (v), l_ (l), c_ (c) +{ +} + +inline lexer::xchar:: +operator char_type () const +{ + return traits_type::to_char_type (v_); +} + +inline lexer::xchar::int_type lexer::xchar:: +value () const +{ + return v_; +} + +inline std::size_t lexer::xchar:: +line () const +{ + return l_; +} + +inline std::size_t lexer::xchar:: +column () const +{ + return c_; +} + +// lexer +// +inline bool lexer:: +valid () const +{ + return valid_; +} + +inline bool lexer:: +is_alpha (char c) const +{ + return std::isalpha (c, loc_); +} + +inline bool lexer:: +is_oct_digit (char c) const +{ + return std::isdigit (c, loc_) && c != '8' && c != '9'; +} + +inline bool lexer:: +is_dec_digit (char c) const +{ + return std::isdigit (c, loc_); +} + +inline bool lexer:: +is_hex_digit (char c) const +{ + return std::isxdigit (c, loc_); +} + +inline bool lexer:: +is_alnum (char c) const +{ + return std::isalnum (c, loc_); +} + +inline bool lexer:: +is_space (char c) const +{ + return std::isspace (c, loc_); +} + +inline bool lexer:: +is_eos (xchar const& c) const +{ + return c.value () == xchar::traits_type::eof (); +} + +inline char lexer:: +to_upper (char c) const +{ + return std::toupper (c, loc_); +} diff --git a/cli/cli/lexer.test.cxx b/cli/cli/lexer.test.cxx new file mode 100644 index 0000000..0eb4dcb --- /dev/null +++ b/cli/cli/lexer.test.cxx @@ -0,0 +1,122 @@ +// file : cli/lexer.test.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <fstream> +#include <iostream> + +#include <cli/token.hxx> +#include <cli/lexer.hxx> + +using namespace std; + +const char* keywords[] = +{ + "source", + "include", + "namespace", + "class", + "signed", + "unsigned", + "bool", + "char", + "wchar_t", + "short", + "int", + "long", + "float", + "double" +}; + +const char* punctuation[] = { + ";", ",", ":", "::", "{", "}", /*"(", ")",*/ "=", "|"}; + +int +main (int argc, char* argv[]) +{ + if (argc != 2) + { + cerr << "usage: " << argv[0] << " file.cli" << endl; + return 1; + } + + ifstream ifs; + ifs.exceptions (ifstream::failbit | ifstream::badbit); + ifs.open (argv[1]); + + lexer l (ifs, argv[1]); + + while (true) + { + token t (l.next ()); + + switch (t.type ()) + { + case token::t_eos: + { + cout << "<EOS>" << endl; + return 0; + } + case token::t_keyword: + { + cout << "keyword: " << keywords[t.keyword ()] << endl; + break; + } + case token::t_identifier: + { + cout << "identifier: " << t.identifier () << endl; + break; + } + case token::t_punctuation: + { + cout << punctuation[t.punctuation ()] << endl; + break; + } + case token::t_cxx_path_lit: + { + cout << "c++ path: " << t.literal () << endl; + break; + } + case token::t_cli_path_lit: + { + cout << "cli path: " << t.literal () << endl; + break; + } + case token::t_string_lit: + { + cout << t.literal () << endl; + break; + } + case token::t_char_lit: + { + cout << t.literal () << endl; + break; + } + case token::t_bool_lit: + { + cout << t.literal () << endl; + break; + } + case token::t_int_lit: + { + cout << t.literal () << endl; + break; + } + case token::t_float_lit: + { + cout << t.literal () << endl; + break; + } + case token::t_call_expr: + { + cout << t.expression () << endl; + break; + } + case token::t_template_expr: + { + cout << t.expression () << endl; + break; + } + } + } +} diff --git a/cli/cli/lexer.test.testscript b/cli/cli/lexer.test.testscript new file mode 100644 index 0000000..e2df5f6 --- /dev/null +++ b/cli/cli/lexer.test.testscript @@ -0,0 +1,191 @@ +# file : cli/lexer.test.testscript +# license : MIT; see accompanying LICENSE file + +# @@ Give tests some meaningfull descriptions. +# + +: 000 +: +cat <<EOI >=test.cli; +help +help-me +-h +--help +--help-me +--help-me- +/h +/help-me +/help/me +--_ + +EOI +$* test.cli >>EOO +identifier: help +identifier: help-me +identifier: -h +identifier: --help +identifier: --help-me +identifier: --help-me- +identifier: /h +identifier: /help-me +identifier: /help +identifier: /me +identifier: --_ +<EOS> +EOO + +: 001 +: +cat <<EOI >=test.cli; +5 +123456 +-12345 +- 1 +- +123 +EOI +$* test.cli >>EOO +5 +123456 +-12345 +-1 +-123 +<EOS> +EOO + +: 002 +: +cat <<EOI >=test.cli; +'a' +'\n' +'\\' +'\0' +'\'' +'\xaf' +'\111' +EOI +$* test.cli >>EOO +'a' +'\n' +'\\' +'\0' +'\'' +'\xaf' +'\111' +<EOS> +EOO + +: 003 +: +cat <<EOI >=test.cli; +"abc"; +"a\nb"; +"abc\\"; +"aaa\0"; +"\""; +"a\xaf"; +"a\111"; +"abc""def"; +"abc" "def"; +"abc + def + + xyz"; +EOI +$* test.cli >>EOO +"abc" +; +"a\nb" +; +"abc\\" +; +"aaa\0" +; +"\"" +; +"a\xaf" +; +"a\111" +; +"abc""def" +; +"abc""def" +; +"abc + def + + xyz" +; +<EOS> +EOO + +: 004 +: +cat <<EOI >=test.cli; +include "foo/abc.hxx"; +include <vector>; +include "c++:map"; +include <cli:map>; +include "map.cli" +EOI +$* test.cli >>EOO +keyword: include +c++ path: "foo/abc.hxx" +; +keyword: include +c++ path: <vector> +; +keyword: include +c++ path: "map" +; +keyword: include +cli path: <map> +; +keyword: include +cli path: "map.cli" +<EOS> +EOO + +: 005 +: +cat <<EOI >=test.cli; +(abc, 123 - 345, 12.34) +<foo, bar::baz, 123*345> +EOI +$* test.cli >>EOO +(abc, 123 - 345, 12.34) +<foo, bar::baz, 123*345> +<EOS> +EOO + +: 006 +: +cat <<EOI >=test.cli; +// c++ comment ; +/* c comment ; */ +; +"a" // foo +"b" +"a" /* foo +bar +baz */ "b"; +- // aaa +5; +- /* a +a +a*/ 5 +// eos +: +:: +EOI +$* test.cli >>EOO +; +"a""b""a""b" +; +-5 +; +-5 +: +:: +<EOS> +EOO diff --git a/cli/cli/man.cxx b/cli/cli/man.cxx new file mode 100644 index 0000000..df703e8 --- /dev/null +++ b/cli/cli/man.cxx @@ -0,0 +1,278 @@ +// file : cli/man.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <vector> +#include <iostream> + +#include <cli/man.hxx> + +using namespace std; + +namespace +{ + // According to groff_mdoc(7), groff may have issues with any of the + // following characters at the beginning of the line: + // + // {}+-/*%<>=,&`'" + // + // Plus, escaping leading '.' with '\' is not sufficient. + // + static const string escape ("{}+-/*%<>=,&`'\""); + + static string + escape_line (const string& s, size_t b, size_t e) + { + string r; + size_t n (e - b); + + + if (escape.find (s[b]) != string::npos || + (n > 1 && s[b] == '\\' && s[b + 1] == '.')) + r = "\\&"; + + r.append (s, b, n); + return r; + } + + static void + wrap_lines (ostream& os, const string& d) + { + size_t b (0), e (0), i (0); + for (size_t n (d.size ()); i < n; ++i) + { + // First handle preformatted text (.nf/.fi). + // + if (d.compare (i, 4, ".nf\n") == 0 && (i == 0 || d[i - 1] == '\n')) + { + assert (b == i); // We should have nothing accumulated. + + // Output everything until (and including) closing .fi as is. + // + e = d.find ("\n.fi", i + 4); + assert (e != string::npos); + e += 4; // Now points past 'i'. + + os << string (d, i, e - i); + + b = e; + i = e - 1; // For ++i in loop header. + continue; + } + + if (d[i] == ' ' || d[i] == '\n') + e = i; + + if (d[i] == '\n' || (i - b >= 78 && e != b)) + { + os << escape_line (d, b, e) << endl; + b = e = e + 1; + } + } + + // Flush the last line. + // + if (b != i) + os << escape_line (d, b, i); + } + + struct doc: traversal::doc, context + { + doc (context& c, class_doc_type cd): context (c), cd_ (cd) {} + + virtual void + traverse (type& ds) + { + if (ds.name ().compare (0, 3, "doc") != 0) // Ignore doc variables. + return; + + // n = 1 - common doc string + // n = 2 - arg string, common doc string + // n > 2 - arg string, short string, long string + // + size_t n (ds.size ()); + const string& d ( + n == 1 + ? (cd_ == cd_short ? first_sentence (ds[0]) : ds[0]) + : (n == 2 + ? (cd_ == cd_short ? first_sentence (ds[1]) : ds[1]) + : ds[cd_ == cd_short ? 1 : 2])); + + std::set<string> arg_set; + if (n > 1) + translate_arg (ds[0], arg_set); + + string s (format (ds.scope (), translate (d, arg_set), true)); + + if (s.empty ()) + return; + + wrap_lines (os, s); + os << endl; + } + + private: + class_doc_type cd_; + }; + + struct option: traversal::option, context + { + option (context& c, class_doc_type cd) : context (c), cd_ (cd) {} + + virtual void + traverse (type& o) + { + using semantics::names; + + semantics::doc_strings const& doc (o.doc ()); + + if (options.suppress_undocumented () && doc.empty ()) + return; + + names& n (o.named ()); + + os << ".IP \"\\fB"; + + for (names::name_iterator i (n.name_begin ()); i != n.name_end (); ++i) + { + if (i != n.name_begin ()) + os << "\\fR|\\fB"; + + os << *i; + } + + os << "\\fR"; + + string type (o.type ().name ()); + + std::set<string> arg_set; + + if (type != "bool" || doc.size () >= 3) + { + string s ( + translate_arg ( + doc.size () > 0 ? doc[0] : string ("<arg>"), arg_set)); + + os << ' ' << format (o.scope (), s, false); + } + + os << "\"" << endl; + + string d; + if (type == "bool" && doc.size () < 3) + { + if (doc.size () > 1) + d = doc[cd_ == cd_short ? 0 : 1]; + else if (doc.size () > 0) + d = (cd_ == cd_short ? first_sentence (doc[0]) : doc[0]); + } + else + { + if (doc.size () > 2) + d = doc[cd_ == cd_short ? 1 : 2]; + else if (doc.size () > 1) + d = (cd_ == cd_short ? first_sentence (doc[1]) : doc[1]); + } + + // Format the documentation string. + // + d = format (o.scope (), translate (d, arg_set), false); + + wrap_lines (os, d); + os << endl; + } + + private: + class_doc_type cd_; + }; + + // + // + struct class_: traversal::class_, context + { + class_ (context& c): context (c), base_ (false) + { + *this >> inherits_ >> *this; + } + + virtual void + traverse (type& c) + { + class_doc_type cd (class_doc (c)); + + if (cd == cd_exclude || (base_ && cd == cd_exclude_base)) + return; + + if (!options.exclude_base () && !options.include_base_last ()) + { + bool ob (base_); + base_ = true; + inherits (c); + base_ = ob; + } + + doc dc (*this, cd); + option op (*this, cd); + traversal::names n; + n >> dc; + n >> op; + names (c, n); + + if (!options.exclude_base () && options.include_base_last ()) + { + bool ob (base_); + base_ = true; + inherits (c); + base_ = ob; + } + } + + private: + bool base_; + traversal::inherits inherits_; + }; +} + +void +generate_man (context& ctx) +{ + traversal::cli_unit unit; + traversal::names unit_names; + traversal::namespace_ ns; + doc dc (ctx, cd_default); + class_ cl (ctx); + unit >> unit_names; + unit_names >> ns; + unit_names >> dc; + unit_names >> cl; + + traversal::names ns_names; + ns >> ns_names; + ns_names >> ns; + ns_names >> dc; + ns_names >> cl; + + if (ctx.options.class_ ().empty ()) + unit.dispatch (ctx.unit); + else + { + for (vector<string>::const_iterator i (ctx.options.class_ ().begin ()); + i != ctx.options.class_ ().end (); ++i) + { + string n (*i); + + // Strip leading :: if present. + // + if (n.size () > 2 && n[0] == ':' && n[1] == ':') + n = string (n, 2, string::npos); + + if (semantics::class_* c = ctx.unit.lookup<semantics::class_> ("", n)) + cl.traverse (*c); + else + { + cerr << "error: class '" << *i << "' not found" << endl; + throw generation_failed (); + } + } + } +} diff --git a/cli/cli/man.hxx b/cli/cli/man.hxx new file mode 100644 index 0000000..0825305 --- /dev/null +++ b/cli/cli/man.hxx @@ -0,0 +1,13 @@ +// file : cli/man.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_MAN_HXX +#define CLI_MAN_HXX + +#include <cli/context.hxx> + +void +generate_man (context&); + +#endif // CLI_MAN_HXX diff --git a/cli/cli/name-processor.cxx b/cli/cli/name-processor.cxx new file mode 100644 index 0000000..ab125bc --- /dev/null +++ b/cli/cli/name-processor.cxx @@ -0,0 +1,193 @@ +// file : cli/name-processor.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <set> +#include <sstream> + +#include <cli/context.hxx> +#include <cli/name-processor.hxx> + +using namespace std; + +namespace +{ + typedef set<string> name_set; + typedef ::context context_base; + + struct context: context_base + { + context (context_base& c): context_base (c) {} + context (context& c): context_base (c) {} + + public: + string + find_name (string const& n, string const& suffix, name_set& set) + { + string name (escape (n + suffix)); + + for (size_t i (1); set.find (name) != set.end (); ++i) + { + ostringstream os; + os << i; + name = escape (n + os.str () + suffix); + } + + set.insert (name); + return name; + } + + string + find_name (string const& n, name_set& set) + { + return find_name (n, "", set); + } + }; + + struct primary_option: traversal::option, context + { + primary_option (context& c, name_set& set) + : context (c), set_ (set) + { + } + + virtual void + traverse (type& o) + { + string n (o.name ()), name; + + // Get rid of leading special characters, e.f., -, --, /, etc. + // + for (size_t i (0); i < n.size (); ++i) + { + if (isalpha (n[i]) || n[i] == '_') + { + name.assign (n.c_str (), i, n.size () - i); + break; + } + } + + o.context ().set ("name", find_name (name, set_)); + } + + private: + name_set& set_; + }; + + struct intermediate_option: traversal::option, context + { + intermediate_option (context& c, name_set& set) + : context (c), set_ (set) + { + } + + virtual void + traverse (type& o) + { + if (gen_specifier && o.type ().name () != "bool") + { + semantics::context& oc (o.context ()); + string const& base (oc.get<string> ("name")); + oc.set ("specifier", find_name (base + "_specified", set_)); + } + } + + private: + name_set& set_; + }; + + struct secondary_option: traversal::option, context + { + secondary_option (context& c, name_set& set) + : context (c), set_ (set) + { + } + + virtual void + traverse (type& o) + { + semantics::context& oc (o.context ()); + string const& base (oc.get<string> ("name")); + oc.set ("member", find_name (base + "_", set_)); + + if (gen_specifier && o.type ().name () != "bool") + { + string const& base (oc.get<string> ("specifier")); + oc.set ("specifier-member", find_name (base + "_", set_)); + } + } + + private: + name_set& set_; + }; + + struct class_: traversal::class_, context + { + class_ (context& c) : context (c) {} + + virtual void + traverse (type& c) + { + semantics::context& cc (c.context ()); + + cc.set ("member-name-set", name_set ()); + name_set& member_set (cc.get<name_set> ("member-name-set")); + + member_set.insert (escape (c.name ())); + + // First assign primary names. + // + { + primary_option option (*this, member_set); + traversal::names names (option); + + class_::names (c, names); + } + + // Then assign intermediate names. + // + { + intermediate_option option (*this, member_set); + traversal::names names (option); + + class_::names (c, names); + } + + // Finally assign secondary names. + // + { + secondary_option option (*this, member_set); + traversal::names names (option); + + class_::names (c, names); + } + } + }; + + void + process_names_ (context_base& c) + { + context ctx (c); + + traversal::cli_unit unit; + traversal::names unit_names; + traversal::namespace_ ns; + class_ cl (ctx); + + unit >> unit_names >> ns; + unit_names >> cl; + + traversal::names ns_names; + + ns >> ns_names >> ns; + ns_names >> cl; + + unit.dispatch (ctx.unit); + } +} + +void +process_names (context_base& c) +{ + process_names_ (c); +} diff --git a/cli/cli/name-processor.hxx b/cli/cli/name-processor.hxx new file mode 100644 index 0000000..c21561b --- /dev/null +++ b/cli/cli/name-processor.hxx @@ -0,0 +1,13 @@ +// file : cli/name-processor.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_NAME_PROCESSOR_HXX +#define CLI_NAME_PROCESSOR_HXX + +#include <cli/context.hxx> + +void +process_names (context&); + +#endif // CLI_NAME_PROCESSOR_HXX diff --git a/cli/cli/option-types.cxx b/cli/cli/option-types.cxx new file mode 100644 index 0000000..da1f434 --- /dev/null +++ b/cli/cli/option-types.cxx @@ -0,0 +1,43 @@ +// file : cli/option-types.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <istream> + +#include <cli/option-types.hxx> + +using namespace std; + +static const char* cxx_version_[] = +{ + "c++98", + "c++11" + "c++14" +}; + +string cxx_version:: +string () const +{ + return cxx_version_[v_]; +} + +istream& +operator>> (istream& is, cxx_version& v) +{ + string s; + is >> s; + + if (!is.fail ()) + { + if (s == "c++98") + v = cxx_version::cxx98; + else if (s == "c++11") + v = cxx_version::cxx11; + else if (s == "c++14") + v = cxx_version::cxx14; + else + is.setstate (istream::failbit); + } + + return is; +} diff --git a/cli/cli/option-types.hxx b/cli/cli/option-types.hxx new file mode 100644 index 0000000..02d9ede --- /dev/null +++ b/cli/cli/option-types.hxx @@ -0,0 +1,33 @@ +// file : cli/option-types.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_OPTION_TYPES_HXX +#define CLI_OPTION_TYPES_HXX + +#include <iosfwd> +#include <string> + +struct cxx_version +{ + enum value + { + cxx98, + cxx11, + cxx14 + }; + + cxx_version (value v = value (0)) : v_ (v) {} + operator value () const {return v_;} + + std::string + string () const; + +private: + value v_; +}; + +std::istream& +operator>> (std::istream&, cxx_version&); + +#endif // CLI_OPTION_TYPES_HXX diff --git a/cli/cli/options.cli b/cli/cli/options.cli new file mode 100644 index 0000000..e564bc4 --- /dev/null +++ b/cli/cli/options.cli @@ -0,0 +1,662 @@ +// file : cli/options.cli +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +// NOTE: Make sure you have a working CLI compiler around before +// modifying this file. +// + +include <map>; +include <string>; +include <vector>; +include <cstddef>; + +include <cli/option-types.hxx>; + +class options +{ + bool --build2-metadata; // Leave undocumented/hidden. + + bool --help {"Print usage information and exit."}; + bool --version {"Print version and exit."}; + + std::vector<std::string> --include-path | -I + { + "<dir>", + "Search <dir> for bracket-included (\cb{<>}) options files." + }; + + std::string --output-dir | -o + { + "<dir>", + "Write the generated files to <dir> instead of the current directory." + }; + + cxx_version --std = cxx_version::cxx98 + { + "<version>", + "Specify the C++ standard that should be used during compilation. + Valid values are \cb{c++98} (default), \cb{c++11}, and \cb{c++14}." + }; + + bool --generate-modifier + { + "Generate option value modifiers in addition to accessors." + }; + + bool --generate-specifier + { + "Generate functions for determining whether the option was specified + on the command line." + }; + + bool --generate-parse + { + "Generate \cb{parse()} functions instead of parsing constructors. This is + primarily useful for being able to parse into an already initialized + options class instance, for example, to implement option + appending/overriding." + }; + + bool --generate-merge + { + "Generate \cb{merge()} functions. This is primarily useful for being able + to merge several already parsed options class instances, for example, to + implement option appending/overriding. Note that this option forces + \cb{--generate-specifier}." + }; + + bool --generate-description + { + "Generate the option description list that can be examined at runtime." + }; + + bool --generate-file-scanner + { + "Generate the \cb{argv_file_scanner} implementation. This scanner is + capable of reading command line arguments from the \cb{argv} array as + well as files specified with command line options." + }; + + bool --generate-vector-scanner + { + "Generate the \cb{vector_scanner} implementation. This scanner is capable + of reading command line arguments from \cb{vector<string>}." + }; + + bool --generate-group-scanner + { + "Generate the \cb{group_scanner} implementation. This scanner supports + grouping of arguments (usually options) to apply only to a certain + argument. + + Groups can be specified before (leading) and/or after (trailing) the + argument they apply to. A leading group starts with '\cb{{}' and ends + with '\cb{\}+}' while a trailing group starts with '\cb{+{}' and ends + with '\cb{\}}'. For example: + + \ + { --foo --bar }+ arg # 'arg' with '--foo' '--bar' + arg +{ fox=1 baz=2 } # 'arg' with 'fox=1' 'baz=2' + \ + + Multiple leading and/or trailing groups can be specified for the + same argument. For example: + + \ + { -f }+ { -b }+ arg +{ f=1 } +{ b=2 } # 'arg' with '-f' 'b' 'f=1' 'b=2' + \ + + Note that the group applies to a single argument only. For example: + + \ + { --foo }+ arg1 arg2 +{ --bar } # 'arg1' with '--foo' and + # 'arg2' with '--bar' + \ + + The group separators ('\cb{{}', '\cb{\}+'}, etc) must be separate command + line arguments. In particular, they must not be adjacent either to the + arguments inside the group nor to the argument they apply to. All such + cases will be treated as ordinary arguments. For example: + + \ + {--foo}+ arg # '{--foo}+' ... + arg+{ --foo } # 'arg+{' ... + \ + + If one of the group separators needs to be specified as an argument + verbatim, then it must be escaped with '\cb{\\}'. For example: + + \ + } # error: unexpected group separator + }x # '}x' + \} # '}' + { \}+ }+ arg # 'arg' with '}+' + \ + " + }; + + bool --suppress-inline + { + "Generate all functions non-inline. By default simple functions are + made inline. This option suppresses creation of the inline file." + }; + + bool --suppress-cli + { + "Do not generate the CLI support types (scanners, parser, etc). Normally, + the support types are generated unless another \cb{.cli} was included, + in which case the support types are expected to be provided by its + generated code." + }; + + std::string --cli-namespace = "::cli" + { + "<ns>", + "Generate the CLI support types in the <ns> namespace (\cb{cli} by + default). The namespace can be nested, for example \cb{details::cli}. + If the namespace is empty, then the support types are generated in + the global namespace." + }; + + std::string --ostream-type = "::std::ostream" + { + "<type>", + "Output stream type instead of the default \cb{std::ostream} that + should be used to print usage and exception information." + }; + + bool --generate-cxx + { + "Generate C++ code. If neither \cb{--generate-man}, \cb{--generate-html}, + nor \cb{--generate-txt} is specified, this mode is assumed by default." + }; + + bool --generate-man + { + "Generate documentation in the man page format." + }; + + bool --generate-html + { + "Generate documentation in the HTML format." + }; + + bool --generate-txt + { + "Generate documentation in the plain text format, similar to usage." + }; + + bool --stdout + { + "Write output to STDOUT instead of a file. This option is not valid + when generating C++ code and is normally used to combine generated + documentation for several option classes in a single file." + }; + + bool --suppress-undocumented + { + "Suppress the generation of documentation entries for undocumented + options." + }; + + bool --suppress-usage + { + "Suppress the generation of the usage printing code." + }; + + bool --long-usage + { + "If no short documentation string is provided, use the complete + long documentation string in usage. By default, in this situation + only the first sentence from the long string is used." + }; + + bool --short-usage + { + "If specified together with \cb{--long-usage}, generate both short + and long usage versions. In this mode, the long usage printing function + is called \cb{print_long_usage()} and in its implementation the long + documentation string is always used, even if the short version is + provided." + }; + + std::string --page-usage + { + "<name>", + "Generate the combined usage printing code for the entire page. + Specifically, this will include all the namespace-level documentation as + well as usage for all the options classes printed in the order they are + defined in the main translation unit (documentation/classes from included + units are ignored except for base classes). + + The <name> argument is used as a prefix to form the name of the usage + printing function. It can include the namespace qualification as well + as documentation variable expansion, for example: + + \ + --page-usage print_ # print_usage() in global namespace + --page-usage app::print_ # print_usage() in app namespace + --page-usage print_$name$_ # print_foo_usage() if name is foo + \ + + If both \cb{--long-usage} and \cb{--short-usage} options are specified, + then the long usage function has the \cb{*long_usage()} suffix." + }; + + std::size_t --option-length = 0 + { + "<len>", + "Indent option descriptions <len> characters when printing usage. This is + useful when you have multiple options classes, potentially in separate + files, and would like their usage to have the same indentation level." + }; + + bool --ansi-color + { + "Use ANSI color escape sequences when printing usage. By \"color\" we + really only mean the bold and underline modifiers. Note that Windows + console does not recognize ANSI escape sequences and will display + them as garbage. However, if you pipe such output through \cb{less(1)}, + it will display them correctly." + }; + + bool --exclude-base + { + "Exclude base class information from usage and documentation." + }; + + bool --include-base-last + { + "Include base class information after derived for usage and documentation. + By default, base classes are included first." + }; + + std::map<std::string, std::string> --class-doc + { + "<name>=<kind>", + "Specify the documentation <kind> that should be used for the options class + <name>. The <name> value should be a fully-qualified class name, for + example, \cb{app::options}. The <kind> value can be \cb{short}, + \cb{long}, \cb{exclude}, or \cb{exclude-base}. If the value is + \cb{exclude}, then the class documentation is excluded from usage and + man/HTML/text output. If it is \cb{exclude-base}, then it is only + excluded when used as a base. For usage, the \cb{short} and \cb{long} + values determine which usage function will be called when the class is + used as base or as part of the page usage (see \cb{--page-usage}). For + man/HTML/text, these values determine which documentation strings are + used in the output." + }; + + std::vector<std::string> --class + { + "<name>", + "Generate the man page, HTML, or text documentation only for the options + class <name>. The <name> value should be a fully-qualified options class + name, for example, \cb{app::options}. To generate documentation for + multiple classes, repeat this option and the documentation will be + produced in the order specified. This functionality is useful if you need + to assemble documentation from multiple classes in a specific order or to + insert custom documentation between options belonging to different + classes." + }; + + std::map<std::string, std::string> --docvar|-v + { + "<name>=<val>", + "Set documentation variable <name> to the value <val>. Documentation + variables can be substituted in prologues and epilogues (see + \cb{--*-prologue*} and \cb{--*-epilogue*} options) using the + \cb{$}<name>\cb{$} expansion syntax (use \cb{$$} to escape expansion). + They can also be defined in \cb{.cli} files using the + \c{\"\\<name>=<val>\"} syntax." + }; + + std::vector<std::string> --link-regex + { + "<regex>", + "Add <regex> to the list of regular expressions used to transform link + targets in the generated documentation. The argument to this option + is a Perl-like regular expression in the form + \c{\b{/}\i{pattern}\b{/}\i{replacement}\b{/}}. Any character can be + used as a delimiter instead of '\cb{/}' and the delimiter can be escaped + inside \ci{pattern} and \ci{replacement} with a backslash (\cb{\\}). + You can specify multiple regular expressions by repeating this option. + All the regular expressions are tried in the order specified and the + first expression that matches is used. Use the \cb{--link-regex-trace} + option to debug link transformation." + }; + + bool --link-regex-trace + { + "Trace the process of applying regular expressions specified with the + \cb{--link-regex} option. Use this option to find out why your regular + expressions don't do what you expected them to do." + }; + + std::map<char, std::string> --html-heading-map + { + "<c>=<h>", + "Map CLI heading <c> (valid values: '\cb{H}', '\cb{0}', '\cb{1}', + '\cb{h}', and '\cb{2}') to HTML heading <h> (for example, '\cb{h1}', + '\cb{h2}', etc)." + }; + + bool --omit-link-check + { + "Don't check that local fragment link references (\\l{#ref ...}) resolve + to ids." + }; + + // Prologues. + // + std::vector<std::string> --hxx-prologue + { + "<text>", + "Insert <text> at the beginning of the generated C++ header file." + }; + + std::vector<std::string> --ixx-prologue + { + "<text>", + "Insert <text> at the beginning of the generated C++ inline file." + }; + + std::vector<std::string> --cxx-prologue + { + "<text>", + "Insert <text> at the beginning of the generated C++ source file." + }; + + std::vector<std::string> --man-prologue + { + "<text>", + "Insert <text> at the beginning of the generated man page file." + }; + + std::vector<std::string> --html-prologue + { + "<text>", + "Insert <text> at the beginning of the generated HTML file." + }; + + std::vector<std::string> --txt-prologue + { + "<text>", + "Insert <text> at the beginning of the generated text file." + }; + + // Epilogues. + // + std::vector<std::string> --hxx-epilogue + { + "<text>", + "Insert <text> at the end of the generated C++ header file." + }; + + std::vector<std::string> --ixx-epilogue + { + "<text>", + "Insert <text> at the end of the generated C++ inline file." + }; + + std::vector<std::string> --cxx-epilogue + { + "<text>", + "Insert <text> at the end of the generated C++ source file." + }; + + std::vector<std::string> --man-epilogue + { + "<text>", + "Insert <text> at the end of the generated man page file." + }; + + std::vector<std::string> --html-epilogue + { + "<text>", + "Insert <text> at the end of the generated HTML file." + }; + + std::vector<std::string> --txt-epilogue + { + "<text>", + "Insert <text> at the end of the generated text file." + }; + + // Prologue files. + // + std::string --hxx-prologue-file + { + "<file>", + "Insert the content of <file> at the beginning of the generated C++ + header file." + }; + + std::string --ixx-prologue-file + { + "<file>", + "Insert the content of <file> at the beginning of the generated C++ + inline file." + }; + + std::string --cxx-prologue-file + { + "<file>", + "Insert the content of <file> at the beginning of the generated C++ + source file." + }; + + std::string --man-prologue-file + { + "<file>", + "Insert the content of <file> at the beginning of the generated man + page file." + }; + + std::string --html-prologue-file + { + "<file>", + "Insert the content of <file> at the beginning of the generated HTML + file." + }; + + std::string --txt-prologue-file + { + "<file>", + "Insert the content of <file> at the beginning of the generated text + file." + }; + + // Epilogue files. + // + std::string --hxx-epilogue-file + { + "<file>", + "Insert the content of <file> at the end of the generated C++ header + file." + }; + + std::string --ixx-epilogue-file + { + "<file>", + "Insert the content of <file> at the end of the generated C++ inline + file." + }; + + std::string --cxx-epilogue-file + { + "<file>", + "Insert the content of <file> at the end of the generated C++ source + file." + }; + + std::string --man-epilogue-file + { + "<file>", + "Insert the content of <file> at the end of the generated man page file." + }; + + std::string --html-epilogue-file + { + "<file>", + "Insert the content of <file> at the end of the generated HTML file." + }; + + std::string --txt-epilogue-file + { + "<file>", + "Insert the content of <file> at the end of the generated text file." + }; + + // Output. + // + std::string --output-prefix + { + "<prefix>", + "Add <prefix> at the beginning of the generated output file name(s)." + }; + + std::string --output-suffix + { + "<suffix>", + "Add <suffix> at the end of the generated output file name(s). Note that + it is added before any file type-specific suffixes; see \cb{--*-suffix} + below." + }; + + std::string --hxx-suffix = ".hxx" + { + "<suffix>", + "Use <suffix> instead of the default \cb{.hxx} to construct the name of + the generated header file." + }; + + std::string --ixx-suffix = ".ixx" + { + "<suffix>", + "Use <suffix> instead of the default \cb{.ixx} to construct the name of + the generated inline file." + }; + + std::string --cxx-suffix = ".cxx" + { + "<suffix>", + "Use <suffix> instead of the default \cb{.cxx} to construct the name of + the generated source file." + }; + + std::string --man-suffix = ".1" + { + "<suffix>", + "Use <suffix> instead of the default \cb{.1} to construct the name of + the generated man page file." + }; + + std::string --html-suffix = ".html" + { + "<suffix>", + "Use <suffix> instead of the default \cb{.html} to construct the name + of the generated HTML file." + }; + + std::string --txt-suffix = ".txt" + { + "<suffix>", + "Use <suffix> instead of the default \cb{.txt} to construct the name of + the generated text file." + }; + + std::string --option-prefix = "-" + { + "<prefix>", + "Use <prefix> instead of the default '\cb{-}' as an option prefix. Unknown + command line arguments that start with this prefix are treated as unknown + options. If you set the option prefix to the empty value, then all the + unknown command line arguments will be treated as program arguments." + }; + + std::string --option-separator = "--" + { + "<sep>", + "Use <sep> instead of the default '\cb{--}' as an optional separator + between options and arguments. All the command line arguments that are + parsed after this separator are treated as program arguments. Set the + option separator to the empty value if you don't want this functionality." + }; + + bool --keep-separator + { + "Leave the option separator in the scanner. This is primarily useful for + incremental option parsing." + }; + + bool --no-combined-flags + { + "Disable support for combining multiple single-character flags into a + single argument (the \cb{-xyz} form that is equivalent to \cb{-x} \cb{-y} + \cb{-z}). An argument is considered a combination of flags if it starts + with a single option prefix (\cb{--option-prefix}) and only contains + letters and digits. Note that an option with a value may not be part of + such a combination, not even if it is specified last." + } + + bool --no-combined-values + { + "Disable support for combining an option and its value into a single + argument with the assignment sign (the \c{\i{option}\b{=}\i{value}} + form). This functionality requires a non-empty option prefix + (\cb{--option-prefix})." + } + + bool --include-with-brackets + { + "Use angle brackets (\cb{<>}) instead of quotes (\cb{\"\"}) in the + generated \cb{#include} directives." + }; + + std::string --include-prefix + { + "<prefix>", + "Add <prefix> to the generated \cb{#include} directive paths." + }; + + std::string --guard-prefix + { + "<prefix>", + "Add <prefix> to the generated header inclusion guards. The prefix is + transformed to upper case and characters that are illegal in a + preprocessor macro name are replaced with underscores." + }; + + std::map<std::string, std::string> --reserved-name + { + "<name>=<rep>", + "Add <name> with an optional <rep> replacement to the list of names + that should not be used as identifiers. If provided, the replacement + name is used instead. All C++ keywords are already in this list." + }; + + // This is a "fake" option in that it is actually handled by + // argv_file_scanner. We have it here to get the documentation. + // + std::string --options-file + { + "<file>", + "Read additional options from <file>. Each option should appear on a + separate line optionally followed by space or equal sign (\cb{=}) and an + option value. Empty lines and lines starting with \cb{#} are ignored. + Option values can be enclosed in double (\cb{\"}) or single (\cb{'}) + quotes to preserve leading and trailing whitespaces as well as to specify + empty values. If the value itself contains trailing or leading quotes, + enclose it with an extra pair of quotes, for example \cb{'\"x\"'}. + Non-leading and non-trailing quotes are interpreted as being part of the + option value. + + The semantics of providing options in a file is equivalent to providing + the same set of options in the same order on the command line at the + point where the \cb{--options-file} option is specified except that + the shell escaping and quoting is not required. Repeat this option + to specify more than one options file." + }; +}; diff --git a/cli/cli/options.cxx b/cli/cli/options.cxx new file mode 100644 index 0000000..cc22a35 --- /dev/null +++ b/cli/cli/options.cxx @@ -0,0 +1,2146 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +// Begin prologue. +// +// +// End prologue. + +#include <cli/options.hxx> + +#include <map> +#include <set> +#include <string> +#include <vector> +#include <ostream> +#include <sstream> +#include <cstring> +#include <fstream> + +namespace cli +{ + // unknown_option + // + unknown_option:: + ~unknown_option () throw () + { + } + + void unknown_option:: + print (::std::ostream& os) const + { + os << "unknown option '" << option ().c_str () << "'"; + } + + const char* unknown_option:: + what () const throw () + { + return "unknown option"; + } + + // unknown_argument + // + unknown_argument:: + ~unknown_argument () throw () + { + } + + void unknown_argument:: + print (::std::ostream& os) const + { + os << "unknown argument '" << argument ().c_str () << "'"; + } + + const char* unknown_argument:: + what () const throw () + { + return "unknown argument"; + } + + // missing_value + // + missing_value:: + ~missing_value () throw () + { + } + + void missing_value:: + print (::std::ostream& os) const + { + os << "missing value for option '" << option ().c_str () << "'"; + } + + const char* missing_value:: + what () const throw () + { + return "missing option value"; + } + + // invalid_value + // + invalid_value:: + ~invalid_value () throw () + { + } + + void invalid_value:: + print (::std::ostream& os) const + { + os << "invalid value '" << value ().c_str () << "' for option '" + << option ().c_str () << "'"; + + if (!message ().empty ()) + os << ": " << message ().c_str (); + } + + const char* invalid_value:: + what () const throw () + { + return "invalid option value"; + } + + // eos_reached + // + void eos_reached:: + print (::std::ostream& os) const + { + os << what (); + } + + const char* eos_reached:: + what () const throw () + { + return "end of argument stream reached"; + } + + // file_io_failure + // + file_io_failure:: + ~file_io_failure () throw () + { + } + + void file_io_failure:: + print (::std::ostream& os) const + { + os << "unable to open file '" << file ().c_str () << "' or read failure"; + } + + const char* file_io_failure:: + what () const throw () + { + return "unable to open file or read failure"; + } + + // unmatched_quote + // + unmatched_quote:: + ~unmatched_quote () throw () + { + } + + void unmatched_quote:: + print (::std::ostream& os) const + { + os << "unmatched quote in argument '" << argument ().c_str () << "'"; + } + + const char* unmatched_quote:: + what () const throw () + { + return "unmatched quote"; + } + + // scanner + // + scanner:: + ~scanner () + { + } + + // argv_scanner + // + bool argv_scanner:: + more () + { + return i_ < argc_; + } + + const char* argv_scanner:: + peek () + { + if (i_ < argc_) + return argv_[i_]; + else + throw eos_reached (); + } + + const char* argv_scanner:: + next () + { + if (i_ < argc_) + { + const char* r (argv_[i_]); + + if (erase_) + { + for (int i (i_ + 1); i < argc_; ++i) + argv_[i - 1] = argv_[i]; + + --argc_; + argv_[argc_] = 0; + } + else + ++i_; + + return r; + } + else + throw eos_reached (); + } + + void argv_scanner:: + skip () + { + if (i_ < argc_) + ++i_; + else + throw eos_reached (); + } + + // argv_file_scanner + // + int argv_file_scanner::zero_argc_ = 0; + std::string argv_file_scanner::empty_string_; + + bool argv_file_scanner:: + more () + { + if (!args_.empty ()) + return true; + + while (base::more ()) + { + // See if the next argument is the file option. + // + const char* a (base::peek ()); + const option_info* oi = 0; + const char* ov = 0; + + if (!skip_) + { + if ((oi = find (a)) != 0) + { + base::next (); + + if (!base::more ()) + throw missing_value (a); + + ov = base::next (); + } + else if (std::strncmp (a, "-", 1) == 0) + { + if ((ov = std::strchr (a, '=')) != 0) + { + std::string o (a, 0, ov - a); + if ((oi = find (o.c_str ())) != 0) + { + base::next (); + ++ov; + } + } + } + } + + if (oi != 0) + { + if (oi->search_func != 0) + { + std::string f (oi->search_func (ov, oi->arg)); + + if (!f.empty ()) + load (f); + } + else + load (ov); + + if (!args_.empty ()) + return true; + } + else + { + if (!skip_) + skip_ = (std::strcmp (a, "--") == 0); + + return true; + } + } + + return false; + } + + const char* argv_file_scanner:: + peek () + { + if (!more ()) + throw eos_reached (); + + return args_.empty () ? base::peek () : args_.front ().value.c_str (); + } + + const std::string& argv_file_scanner:: + peek_file () + { + if (!more ()) + throw eos_reached (); + + return args_.empty () ? empty_string_ : *args_.front ().file; + } + + std::size_t argv_file_scanner:: + peek_line () + { + if (!more ()) + throw eos_reached (); + + return args_.empty () ? 0 : args_.front ().line; + } + + const char* argv_file_scanner:: + next () + { + if (!more ()) + throw eos_reached (); + + if (args_.empty ()) + return base::next (); + else + { + hold_[i_ == 0 ? ++i_ : --i_].swap (args_.front ().value); + args_.pop_front (); + return hold_[i_].c_str (); + } + } + + void argv_file_scanner:: + skip () + { + if (!more ()) + throw eos_reached (); + + if (args_.empty ()) + return base::skip (); + else + args_.pop_front (); + } + + const argv_file_scanner::option_info* argv_file_scanner:: + find (const char* a) const + { + for (std::size_t i (0); i < options_count_; ++i) + if (std::strcmp (a, options_[i].option) == 0) + return &options_[i]; + + return 0; + } + + void argv_file_scanner:: + load (const std::string& file) + { + using namespace std; + + ifstream is (file.c_str ()); + + if (!is.is_open ()) + throw file_io_failure (file); + + files_.push_back (file); + + arg a; + a.file = &*files_.rbegin (); + + for (a.line = 1; !is.eof (); ++a.line) + { + string line; + getline (is, line); + + if (is.fail () && !is.eof ()) + throw file_io_failure (file); + + string::size_type n (line.size ()); + + // Trim the line from leading and trailing whitespaces. + // + if (n != 0) + { + const char* f (line.c_str ()); + const char* l (f + n); + + const char* of (f); + while (f < l && (*f == ' ' || *f == '\t' || *f == '\r')) + ++f; + + --l; + + const char* ol (l); + while (l > f && (*l == ' ' || *l == '\t' || *l == '\r')) + --l; + + if (f != of || l != ol) + line = f <= l ? string (f, l - f + 1) : string (); + } + + // Ignore empty lines, those that start with #. + // + if (line.empty () || line[0] == '#') + continue; + + string::size_type p (string::npos); + if (line.compare (0, 1, "-") == 0) + { + p = line.find (' '); + + string::size_type q (line.find ('=')); + if (q != string::npos && q < p) + p = q; + } + + string s1; + if (p != string::npos) + { + s1.assign (line, 0, p); + + // Skip leading whitespaces in the argument. + // + if (line[p] == '=') + ++p; + else + { + n = line.size (); + for (++p; p < n; ++p) + { + char c (line[p]); + if (c != ' ' && c != '\t' && c != '\r') + break; + } + } + } + else if (!skip_) + skip_ = (line == "--"); + + string s2 (line, p != string::npos ? p : 0); + + // If the string (which is an option value or argument) is + // wrapped in quotes, remove them. + // + n = s2.size (); + char cf (s2[0]), cl (s2[n - 1]); + + if (cf == '"' || cf == '\'' || cl == '"' || cl == '\'') + { + if (n == 1 || cf != cl) + throw unmatched_quote (s2); + + s2 = string (s2, 1, n - 2); + } + + if (!s1.empty ()) + { + // See if this is another file option. + // + const option_info* oi; + if (!skip_ && (oi = find (s1.c_str ()))) + { + if (s2.empty ()) + throw missing_value (oi->option); + + if (oi->search_func != 0) + { + std::string f (oi->search_func (s2.c_str (), oi->arg)); + if (!f.empty ()) + load (f); + } + else + load (s2); + + continue; + } + + a.value = s1; + args_.push_back (a); + } + + a.value = s2; + args_.push_back (a); + } + } + + template <typename X> + struct parser + { + static void + parse (X& x, bool& xs, scanner& s) + { + using namespace std; + + const char* o (s.next ()); + if (s.more ()) + { + string v (s.next ()); + istringstream is (v); + if (!(is >> x && is.peek () == istringstream::traits_type::eof ())) + throw invalid_value (o, v); + } + else + throw missing_value (o); + + xs = true; + } + }; + + template <> + struct parser<bool> + { + static void + parse (bool& x, scanner& s) + { + s.next (); + x = true; + } + }; + + template <> + struct parser<std::string> + { + static void + parse (std::string& x, bool& xs, scanner& s) + { + const char* o (s.next ()); + + if (s.more ()) + x = s.next (); + else + throw missing_value (o); + + xs = true; + } + }; + + template <typename X> + struct parser<std::vector<X> > + { + static void + parse (std::vector<X>& c, bool& xs, scanner& s) + { + X x; + bool dummy; + parser<X>::parse (x, dummy, s); + c.push_back (x); + xs = true; + } + }; + + template <typename X> + struct parser<std::set<X> > + { + static void + parse (std::set<X>& c, bool& xs, scanner& s) + { + X x; + bool dummy; + parser<X>::parse (x, dummy, s); + c.insert (x); + xs = true; + } + }; + + template <typename K, typename V> + struct parser<std::map<K, V> > + { + static void + parse (std::map<K, V>& m, bool& xs, scanner& s) + { + const char* o (s.next ()); + + if (s.more ()) + { + std::string ov (s.next ()); + std::string::size_type p = ov.find ('='); + + K k = K (); + V v = V (); + std::string kstr (ov, 0, p); + std::string vstr (ov, (p != std::string::npos ? p + 1 : ov.size ())); + + int ac (2); + char* av[] = + { + const_cast<char*> (o), 0 + }; + + bool dummy; + if (!kstr.empty ()) + { + av[1] = const_cast<char*> (kstr.c_str ()); + argv_scanner s (0, ac, av); + parser<K>::parse (k, dummy, s); + } + + if (!vstr.empty ()) + { + av[1] = const_cast<char*> (vstr.c_str ()); + argv_scanner s (0, ac, av); + parser<V>::parse (v, dummy, s); + } + + m[k] = v; + } + else + throw missing_value (o); + + xs = true; + } + }; + + template <typename X, typename T, T X::*M> + void + thunk (X& x, scanner& s) + { + parser<T>::parse (x.*M, s); + } + + template <typename X, typename T, T X::*M, bool X::*S> + void + thunk (X& x, scanner& s) + { + parser<T>::parse (x.*M, x.*S, s); + } +} + +#include <map> +#include <cstring> + +// options +// + +options:: +options () +: build2_metadata_ (), + help_ (), + version_ (), + include_path_ (), + include_path_specified_ (false), + output_dir_ (), + output_dir_specified_ (false), + std_ (cxx_version::cxx98), + std_specified_ (false), + generate_modifier_ (), + generate_specifier_ (), + generate_parse_ (), + generate_merge_ (), + generate_description_ (), + generate_file_scanner_ (), + generate_vector_scanner_ (), + generate_group_scanner_ (), + suppress_inline_ (), + suppress_cli_ (), + cli_namespace_ ("::cli"), + cli_namespace_specified_ (false), + ostream_type_ ("::std::ostream"), + ostream_type_specified_ (false), + generate_cxx_ (), + generate_man_ (), + generate_html_ (), + generate_txt_ (), + stdout__ (), + suppress_undocumented_ (), + suppress_usage_ (), + long_usage_ (), + short_usage_ (), + page_usage_ (), + page_usage_specified_ (false), + option_length_ (0), + option_length_specified_ (false), + ansi_color_ (), + exclude_base_ (), + include_base_last_ (), + class_doc_ (), + class_doc_specified_ (false), + class__ (), + class__specified_ (false), + docvar_ (), + docvar_specified_ (false), + link_regex_ (), + link_regex_specified_ (false), + link_regex_trace_ (), + html_heading_map_ (), + html_heading_map_specified_ (false), + omit_link_check_ (), + hxx_prologue_ (), + hxx_prologue_specified_ (false), + ixx_prologue_ (), + ixx_prologue_specified_ (false), + cxx_prologue_ (), + cxx_prologue_specified_ (false), + man_prologue_ (), + man_prologue_specified_ (false), + html_prologue_ (), + html_prologue_specified_ (false), + txt_prologue_ (), + txt_prologue_specified_ (false), + hxx_epilogue_ (), + hxx_epilogue_specified_ (false), + ixx_epilogue_ (), + ixx_epilogue_specified_ (false), + cxx_epilogue_ (), + cxx_epilogue_specified_ (false), + man_epilogue_ (), + man_epilogue_specified_ (false), + html_epilogue_ (), + html_epilogue_specified_ (false), + txt_epilogue_ (), + txt_epilogue_specified_ (false), + hxx_prologue_file_ (), + hxx_prologue_file_specified_ (false), + ixx_prologue_file_ (), + ixx_prologue_file_specified_ (false), + cxx_prologue_file_ (), + cxx_prologue_file_specified_ (false), + man_prologue_file_ (), + man_prologue_file_specified_ (false), + html_prologue_file_ (), + html_prologue_file_specified_ (false), + txt_prologue_file_ (), + txt_prologue_file_specified_ (false), + hxx_epilogue_file_ (), + hxx_epilogue_file_specified_ (false), + ixx_epilogue_file_ (), + ixx_epilogue_file_specified_ (false), + cxx_epilogue_file_ (), + cxx_epilogue_file_specified_ (false), + man_epilogue_file_ (), + man_epilogue_file_specified_ (false), + html_epilogue_file_ (), + html_epilogue_file_specified_ (false), + txt_epilogue_file_ (), + txt_epilogue_file_specified_ (false), + output_prefix_ (), + output_prefix_specified_ (false), + output_suffix_ (), + output_suffix_specified_ (false), + hxx_suffix_ (".hxx"), + hxx_suffix_specified_ (false), + ixx_suffix_ (".ixx"), + ixx_suffix_specified_ (false), + cxx_suffix_ (".cxx"), + cxx_suffix_specified_ (false), + man_suffix_ (".1"), + man_suffix_specified_ (false), + html_suffix_ (".html"), + html_suffix_specified_ (false), + txt_suffix_ (".txt"), + txt_suffix_specified_ (false), + option_prefix_ ("-"), + option_prefix_specified_ (false), + option_separator_ ("--"), + option_separator_specified_ (false), + keep_separator_ (), + no_combined_flags_ (), + no_combined_values_ (), + include_with_brackets_ (), + include_prefix_ (), + include_prefix_specified_ (false), + guard_prefix_ (), + guard_prefix_specified_ (false), + reserved_name_ (), + reserved_name_specified_ (false), + options_file_ (), + options_file_specified_ (false) +{ +} + +options:: +options (int& argc, + char** argv, + bool erase, + ::cli::unknown_mode opt, + ::cli::unknown_mode arg) +: build2_metadata_ (), + help_ (), + version_ (), + include_path_ (), + include_path_specified_ (false), + output_dir_ (), + output_dir_specified_ (false), + std_ (cxx_version::cxx98), + std_specified_ (false), + generate_modifier_ (), + generate_specifier_ (), + generate_parse_ (), + generate_merge_ (), + generate_description_ (), + generate_file_scanner_ (), + generate_vector_scanner_ (), + generate_group_scanner_ (), + suppress_inline_ (), + suppress_cli_ (), + cli_namespace_ ("::cli"), + cli_namespace_specified_ (false), + ostream_type_ ("::std::ostream"), + ostream_type_specified_ (false), + generate_cxx_ (), + generate_man_ (), + generate_html_ (), + generate_txt_ (), + stdout__ (), + suppress_undocumented_ (), + suppress_usage_ (), + long_usage_ (), + short_usage_ (), + page_usage_ (), + page_usage_specified_ (false), + option_length_ (0), + option_length_specified_ (false), + ansi_color_ (), + exclude_base_ (), + include_base_last_ (), + class_doc_ (), + class_doc_specified_ (false), + class__ (), + class__specified_ (false), + docvar_ (), + docvar_specified_ (false), + link_regex_ (), + link_regex_specified_ (false), + link_regex_trace_ (), + html_heading_map_ (), + html_heading_map_specified_ (false), + omit_link_check_ (), + hxx_prologue_ (), + hxx_prologue_specified_ (false), + ixx_prologue_ (), + ixx_prologue_specified_ (false), + cxx_prologue_ (), + cxx_prologue_specified_ (false), + man_prologue_ (), + man_prologue_specified_ (false), + html_prologue_ (), + html_prologue_specified_ (false), + txt_prologue_ (), + txt_prologue_specified_ (false), + hxx_epilogue_ (), + hxx_epilogue_specified_ (false), + ixx_epilogue_ (), + ixx_epilogue_specified_ (false), + cxx_epilogue_ (), + cxx_epilogue_specified_ (false), + man_epilogue_ (), + man_epilogue_specified_ (false), + html_epilogue_ (), + html_epilogue_specified_ (false), + txt_epilogue_ (), + txt_epilogue_specified_ (false), + hxx_prologue_file_ (), + hxx_prologue_file_specified_ (false), + ixx_prologue_file_ (), + ixx_prologue_file_specified_ (false), + cxx_prologue_file_ (), + cxx_prologue_file_specified_ (false), + man_prologue_file_ (), + man_prologue_file_specified_ (false), + html_prologue_file_ (), + html_prologue_file_specified_ (false), + txt_prologue_file_ (), + txt_prologue_file_specified_ (false), + hxx_epilogue_file_ (), + hxx_epilogue_file_specified_ (false), + ixx_epilogue_file_ (), + ixx_epilogue_file_specified_ (false), + cxx_epilogue_file_ (), + cxx_epilogue_file_specified_ (false), + man_epilogue_file_ (), + man_epilogue_file_specified_ (false), + html_epilogue_file_ (), + html_epilogue_file_specified_ (false), + txt_epilogue_file_ (), + txt_epilogue_file_specified_ (false), + output_prefix_ (), + output_prefix_specified_ (false), + output_suffix_ (), + output_suffix_specified_ (false), + hxx_suffix_ (".hxx"), + hxx_suffix_specified_ (false), + ixx_suffix_ (".ixx"), + ixx_suffix_specified_ (false), + cxx_suffix_ (".cxx"), + cxx_suffix_specified_ (false), + man_suffix_ (".1"), + man_suffix_specified_ (false), + html_suffix_ (".html"), + html_suffix_specified_ (false), + txt_suffix_ (".txt"), + txt_suffix_specified_ (false), + option_prefix_ ("-"), + option_prefix_specified_ (false), + option_separator_ ("--"), + option_separator_specified_ (false), + keep_separator_ (), + no_combined_flags_ (), + no_combined_values_ (), + include_with_brackets_ (), + include_prefix_ (), + include_prefix_specified_ (false), + guard_prefix_ (), + guard_prefix_specified_ (false), + reserved_name_ (), + reserved_name_specified_ (false), + options_file_ (), + options_file_specified_ (false) +{ + ::cli::argv_scanner s (argc, argv, erase); + _parse (s, opt, arg); +} + +options:: +options (int start, + int& argc, + char** argv, + bool erase, + ::cli::unknown_mode opt, + ::cli::unknown_mode arg) +: build2_metadata_ (), + help_ (), + version_ (), + include_path_ (), + include_path_specified_ (false), + output_dir_ (), + output_dir_specified_ (false), + std_ (cxx_version::cxx98), + std_specified_ (false), + generate_modifier_ (), + generate_specifier_ (), + generate_parse_ (), + generate_merge_ (), + generate_description_ (), + generate_file_scanner_ (), + generate_vector_scanner_ (), + generate_group_scanner_ (), + suppress_inline_ (), + suppress_cli_ (), + cli_namespace_ ("::cli"), + cli_namespace_specified_ (false), + ostream_type_ ("::std::ostream"), + ostream_type_specified_ (false), + generate_cxx_ (), + generate_man_ (), + generate_html_ (), + generate_txt_ (), + stdout__ (), + suppress_undocumented_ (), + suppress_usage_ (), + long_usage_ (), + short_usage_ (), + page_usage_ (), + page_usage_specified_ (false), + option_length_ (0), + option_length_specified_ (false), + ansi_color_ (), + exclude_base_ (), + include_base_last_ (), + class_doc_ (), + class_doc_specified_ (false), + class__ (), + class__specified_ (false), + docvar_ (), + docvar_specified_ (false), + link_regex_ (), + link_regex_specified_ (false), + link_regex_trace_ (), + html_heading_map_ (), + html_heading_map_specified_ (false), + omit_link_check_ (), + hxx_prologue_ (), + hxx_prologue_specified_ (false), + ixx_prologue_ (), + ixx_prologue_specified_ (false), + cxx_prologue_ (), + cxx_prologue_specified_ (false), + man_prologue_ (), + man_prologue_specified_ (false), + html_prologue_ (), + html_prologue_specified_ (false), + txt_prologue_ (), + txt_prologue_specified_ (false), + hxx_epilogue_ (), + hxx_epilogue_specified_ (false), + ixx_epilogue_ (), + ixx_epilogue_specified_ (false), + cxx_epilogue_ (), + cxx_epilogue_specified_ (false), + man_epilogue_ (), + man_epilogue_specified_ (false), + html_epilogue_ (), + html_epilogue_specified_ (false), + txt_epilogue_ (), + txt_epilogue_specified_ (false), + hxx_prologue_file_ (), + hxx_prologue_file_specified_ (false), + ixx_prologue_file_ (), + ixx_prologue_file_specified_ (false), + cxx_prologue_file_ (), + cxx_prologue_file_specified_ (false), + man_prologue_file_ (), + man_prologue_file_specified_ (false), + html_prologue_file_ (), + html_prologue_file_specified_ (false), + txt_prologue_file_ (), + txt_prologue_file_specified_ (false), + hxx_epilogue_file_ (), + hxx_epilogue_file_specified_ (false), + ixx_epilogue_file_ (), + ixx_epilogue_file_specified_ (false), + cxx_epilogue_file_ (), + cxx_epilogue_file_specified_ (false), + man_epilogue_file_ (), + man_epilogue_file_specified_ (false), + html_epilogue_file_ (), + html_epilogue_file_specified_ (false), + txt_epilogue_file_ (), + txt_epilogue_file_specified_ (false), + output_prefix_ (), + output_prefix_specified_ (false), + output_suffix_ (), + output_suffix_specified_ (false), + hxx_suffix_ (".hxx"), + hxx_suffix_specified_ (false), + ixx_suffix_ (".ixx"), + ixx_suffix_specified_ (false), + cxx_suffix_ (".cxx"), + cxx_suffix_specified_ (false), + man_suffix_ (".1"), + man_suffix_specified_ (false), + html_suffix_ (".html"), + html_suffix_specified_ (false), + txt_suffix_ (".txt"), + txt_suffix_specified_ (false), + option_prefix_ ("-"), + option_prefix_specified_ (false), + option_separator_ ("--"), + option_separator_specified_ (false), + keep_separator_ (), + no_combined_flags_ (), + no_combined_values_ (), + include_with_brackets_ (), + include_prefix_ (), + include_prefix_specified_ (false), + guard_prefix_ (), + guard_prefix_specified_ (false), + reserved_name_ (), + reserved_name_specified_ (false), + options_file_ (), + options_file_specified_ (false) +{ + ::cli::argv_scanner s (start, argc, argv, erase); + _parse (s, opt, arg); +} + +options:: +options (int& argc, + char** argv, + int& end, + bool erase, + ::cli::unknown_mode opt, + ::cli::unknown_mode arg) +: build2_metadata_ (), + help_ (), + version_ (), + include_path_ (), + include_path_specified_ (false), + output_dir_ (), + output_dir_specified_ (false), + std_ (cxx_version::cxx98), + std_specified_ (false), + generate_modifier_ (), + generate_specifier_ (), + generate_parse_ (), + generate_merge_ (), + generate_description_ (), + generate_file_scanner_ (), + generate_vector_scanner_ (), + generate_group_scanner_ (), + suppress_inline_ (), + suppress_cli_ (), + cli_namespace_ ("::cli"), + cli_namespace_specified_ (false), + ostream_type_ ("::std::ostream"), + ostream_type_specified_ (false), + generate_cxx_ (), + generate_man_ (), + generate_html_ (), + generate_txt_ (), + stdout__ (), + suppress_undocumented_ (), + suppress_usage_ (), + long_usage_ (), + short_usage_ (), + page_usage_ (), + page_usage_specified_ (false), + option_length_ (0), + option_length_specified_ (false), + ansi_color_ (), + exclude_base_ (), + include_base_last_ (), + class_doc_ (), + class_doc_specified_ (false), + class__ (), + class__specified_ (false), + docvar_ (), + docvar_specified_ (false), + link_regex_ (), + link_regex_specified_ (false), + link_regex_trace_ (), + html_heading_map_ (), + html_heading_map_specified_ (false), + omit_link_check_ (), + hxx_prologue_ (), + hxx_prologue_specified_ (false), + ixx_prologue_ (), + ixx_prologue_specified_ (false), + cxx_prologue_ (), + cxx_prologue_specified_ (false), + man_prologue_ (), + man_prologue_specified_ (false), + html_prologue_ (), + html_prologue_specified_ (false), + txt_prologue_ (), + txt_prologue_specified_ (false), + hxx_epilogue_ (), + hxx_epilogue_specified_ (false), + ixx_epilogue_ (), + ixx_epilogue_specified_ (false), + cxx_epilogue_ (), + cxx_epilogue_specified_ (false), + man_epilogue_ (), + man_epilogue_specified_ (false), + html_epilogue_ (), + html_epilogue_specified_ (false), + txt_epilogue_ (), + txt_epilogue_specified_ (false), + hxx_prologue_file_ (), + hxx_prologue_file_specified_ (false), + ixx_prologue_file_ (), + ixx_prologue_file_specified_ (false), + cxx_prologue_file_ (), + cxx_prologue_file_specified_ (false), + man_prologue_file_ (), + man_prologue_file_specified_ (false), + html_prologue_file_ (), + html_prologue_file_specified_ (false), + txt_prologue_file_ (), + txt_prologue_file_specified_ (false), + hxx_epilogue_file_ (), + hxx_epilogue_file_specified_ (false), + ixx_epilogue_file_ (), + ixx_epilogue_file_specified_ (false), + cxx_epilogue_file_ (), + cxx_epilogue_file_specified_ (false), + man_epilogue_file_ (), + man_epilogue_file_specified_ (false), + html_epilogue_file_ (), + html_epilogue_file_specified_ (false), + txt_epilogue_file_ (), + txt_epilogue_file_specified_ (false), + output_prefix_ (), + output_prefix_specified_ (false), + output_suffix_ (), + output_suffix_specified_ (false), + hxx_suffix_ (".hxx"), + hxx_suffix_specified_ (false), + ixx_suffix_ (".ixx"), + ixx_suffix_specified_ (false), + cxx_suffix_ (".cxx"), + cxx_suffix_specified_ (false), + man_suffix_ (".1"), + man_suffix_specified_ (false), + html_suffix_ (".html"), + html_suffix_specified_ (false), + txt_suffix_ (".txt"), + txt_suffix_specified_ (false), + option_prefix_ ("-"), + option_prefix_specified_ (false), + option_separator_ ("--"), + option_separator_specified_ (false), + keep_separator_ (), + no_combined_flags_ (), + no_combined_values_ (), + include_with_brackets_ (), + include_prefix_ (), + include_prefix_specified_ (false), + guard_prefix_ (), + guard_prefix_specified_ (false), + reserved_name_ (), + reserved_name_specified_ (false), + options_file_ (), + options_file_specified_ (false) +{ + ::cli::argv_scanner s (argc, argv, erase); + _parse (s, opt, arg); + end = s.end (); +} + +options:: +options (int start, + int& argc, + char** argv, + int& end, + bool erase, + ::cli::unknown_mode opt, + ::cli::unknown_mode arg) +: build2_metadata_ (), + help_ (), + version_ (), + include_path_ (), + include_path_specified_ (false), + output_dir_ (), + output_dir_specified_ (false), + std_ (cxx_version::cxx98), + std_specified_ (false), + generate_modifier_ (), + generate_specifier_ (), + generate_parse_ (), + generate_merge_ (), + generate_description_ (), + generate_file_scanner_ (), + generate_vector_scanner_ (), + generate_group_scanner_ (), + suppress_inline_ (), + suppress_cli_ (), + cli_namespace_ ("::cli"), + cli_namespace_specified_ (false), + ostream_type_ ("::std::ostream"), + ostream_type_specified_ (false), + generate_cxx_ (), + generate_man_ (), + generate_html_ (), + generate_txt_ (), + stdout__ (), + suppress_undocumented_ (), + suppress_usage_ (), + long_usage_ (), + short_usage_ (), + page_usage_ (), + page_usage_specified_ (false), + option_length_ (0), + option_length_specified_ (false), + ansi_color_ (), + exclude_base_ (), + include_base_last_ (), + class_doc_ (), + class_doc_specified_ (false), + class__ (), + class__specified_ (false), + docvar_ (), + docvar_specified_ (false), + link_regex_ (), + link_regex_specified_ (false), + link_regex_trace_ (), + html_heading_map_ (), + html_heading_map_specified_ (false), + omit_link_check_ (), + hxx_prologue_ (), + hxx_prologue_specified_ (false), + ixx_prologue_ (), + ixx_prologue_specified_ (false), + cxx_prologue_ (), + cxx_prologue_specified_ (false), + man_prologue_ (), + man_prologue_specified_ (false), + html_prologue_ (), + html_prologue_specified_ (false), + txt_prologue_ (), + txt_prologue_specified_ (false), + hxx_epilogue_ (), + hxx_epilogue_specified_ (false), + ixx_epilogue_ (), + ixx_epilogue_specified_ (false), + cxx_epilogue_ (), + cxx_epilogue_specified_ (false), + man_epilogue_ (), + man_epilogue_specified_ (false), + html_epilogue_ (), + html_epilogue_specified_ (false), + txt_epilogue_ (), + txt_epilogue_specified_ (false), + hxx_prologue_file_ (), + hxx_prologue_file_specified_ (false), + ixx_prologue_file_ (), + ixx_prologue_file_specified_ (false), + cxx_prologue_file_ (), + cxx_prologue_file_specified_ (false), + man_prologue_file_ (), + man_prologue_file_specified_ (false), + html_prologue_file_ (), + html_prologue_file_specified_ (false), + txt_prologue_file_ (), + txt_prologue_file_specified_ (false), + hxx_epilogue_file_ (), + hxx_epilogue_file_specified_ (false), + ixx_epilogue_file_ (), + ixx_epilogue_file_specified_ (false), + cxx_epilogue_file_ (), + cxx_epilogue_file_specified_ (false), + man_epilogue_file_ (), + man_epilogue_file_specified_ (false), + html_epilogue_file_ (), + html_epilogue_file_specified_ (false), + txt_epilogue_file_ (), + txt_epilogue_file_specified_ (false), + output_prefix_ (), + output_prefix_specified_ (false), + output_suffix_ (), + output_suffix_specified_ (false), + hxx_suffix_ (".hxx"), + hxx_suffix_specified_ (false), + ixx_suffix_ (".ixx"), + ixx_suffix_specified_ (false), + cxx_suffix_ (".cxx"), + cxx_suffix_specified_ (false), + man_suffix_ (".1"), + man_suffix_specified_ (false), + html_suffix_ (".html"), + html_suffix_specified_ (false), + txt_suffix_ (".txt"), + txt_suffix_specified_ (false), + option_prefix_ ("-"), + option_prefix_specified_ (false), + option_separator_ ("--"), + option_separator_specified_ (false), + keep_separator_ (), + no_combined_flags_ (), + no_combined_values_ (), + include_with_brackets_ (), + include_prefix_ (), + include_prefix_specified_ (false), + guard_prefix_ (), + guard_prefix_specified_ (false), + reserved_name_ (), + reserved_name_specified_ (false), + options_file_ (), + options_file_specified_ (false) +{ + ::cli::argv_scanner s (start, argc, argv, erase); + _parse (s, opt, arg); + end = s.end (); +} + +options:: +options (::cli::scanner& s, + ::cli::unknown_mode opt, + ::cli::unknown_mode arg) +: build2_metadata_ (), + help_ (), + version_ (), + include_path_ (), + include_path_specified_ (false), + output_dir_ (), + output_dir_specified_ (false), + std_ (cxx_version::cxx98), + std_specified_ (false), + generate_modifier_ (), + generate_specifier_ (), + generate_parse_ (), + generate_merge_ (), + generate_description_ (), + generate_file_scanner_ (), + generate_vector_scanner_ (), + generate_group_scanner_ (), + suppress_inline_ (), + suppress_cli_ (), + cli_namespace_ ("::cli"), + cli_namespace_specified_ (false), + ostream_type_ ("::std::ostream"), + ostream_type_specified_ (false), + generate_cxx_ (), + generate_man_ (), + generate_html_ (), + generate_txt_ (), + stdout__ (), + suppress_undocumented_ (), + suppress_usage_ (), + long_usage_ (), + short_usage_ (), + page_usage_ (), + page_usage_specified_ (false), + option_length_ (0), + option_length_specified_ (false), + ansi_color_ (), + exclude_base_ (), + include_base_last_ (), + class_doc_ (), + class_doc_specified_ (false), + class__ (), + class__specified_ (false), + docvar_ (), + docvar_specified_ (false), + link_regex_ (), + link_regex_specified_ (false), + link_regex_trace_ (), + html_heading_map_ (), + html_heading_map_specified_ (false), + omit_link_check_ (), + hxx_prologue_ (), + hxx_prologue_specified_ (false), + ixx_prologue_ (), + ixx_prologue_specified_ (false), + cxx_prologue_ (), + cxx_prologue_specified_ (false), + man_prologue_ (), + man_prologue_specified_ (false), + html_prologue_ (), + html_prologue_specified_ (false), + txt_prologue_ (), + txt_prologue_specified_ (false), + hxx_epilogue_ (), + hxx_epilogue_specified_ (false), + ixx_epilogue_ (), + ixx_epilogue_specified_ (false), + cxx_epilogue_ (), + cxx_epilogue_specified_ (false), + man_epilogue_ (), + man_epilogue_specified_ (false), + html_epilogue_ (), + html_epilogue_specified_ (false), + txt_epilogue_ (), + txt_epilogue_specified_ (false), + hxx_prologue_file_ (), + hxx_prologue_file_specified_ (false), + ixx_prologue_file_ (), + ixx_prologue_file_specified_ (false), + cxx_prologue_file_ (), + cxx_prologue_file_specified_ (false), + man_prologue_file_ (), + man_prologue_file_specified_ (false), + html_prologue_file_ (), + html_prologue_file_specified_ (false), + txt_prologue_file_ (), + txt_prologue_file_specified_ (false), + hxx_epilogue_file_ (), + hxx_epilogue_file_specified_ (false), + ixx_epilogue_file_ (), + ixx_epilogue_file_specified_ (false), + cxx_epilogue_file_ (), + cxx_epilogue_file_specified_ (false), + man_epilogue_file_ (), + man_epilogue_file_specified_ (false), + html_epilogue_file_ (), + html_epilogue_file_specified_ (false), + txt_epilogue_file_ (), + txt_epilogue_file_specified_ (false), + output_prefix_ (), + output_prefix_specified_ (false), + output_suffix_ (), + output_suffix_specified_ (false), + hxx_suffix_ (".hxx"), + hxx_suffix_specified_ (false), + ixx_suffix_ (".ixx"), + ixx_suffix_specified_ (false), + cxx_suffix_ (".cxx"), + cxx_suffix_specified_ (false), + man_suffix_ (".1"), + man_suffix_specified_ (false), + html_suffix_ (".html"), + html_suffix_specified_ (false), + txt_suffix_ (".txt"), + txt_suffix_specified_ (false), + option_prefix_ ("-"), + option_prefix_specified_ (false), + option_separator_ ("--"), + option_separator_specified_ (false), + keep_separator_ (), + no_combined_flags_ (), + no_combined_values_ (), + include_with_brackets_ (), + include_prefix_ (), + include_prefix_specified_ (false), + guard_prefix_ (), + guard_prefix_specified_ (false), + reserved_name_ (), + reserved_name_specified_ (false), + options_file_ (), + options_file_specified_ (false) +{ + _parse (s, opt, arg); +} + +::cli::usage_para options:: +print_usage (::std::ostream& os, ::cli::usage_para p) +{ + CLI_POTENTIALLY_UNUSED (os); + + if (p == ::cli::usage_para::text) + os << ::std::endl; + + os << "--build2-metadata" << std::endl; + + os << "--help Print usage information and exit." << ::std::endl; + + os << "--version Print version and exit." << ::std::endl; + + os << "--include-path|-I <dir> Search <dir> for bracket-included (<>) options" << ::std::endl + << " files." << ::std::endl; + + os << "--output-dir|-o <dir> Write the generated files to <dir> instead of the" << ::std::endl + << " current directory." << ::std::endl; + + os << "--std <version> Specify the C++ standard that should be used" << ::std::endl + << " during compilation." << ::std::endl; + + os << "--generate-modifier Generate option value modifiers in addition to" << ::std::endl + << " accessors." << ::std::endl; + + os << "--generate-specifier Generate functions for determining whether the" << ::std::endl + << " option was specified on the command line." << ::std::endl; + + os << "--generate-parse Generate parse() functions instead of parsing" << ::std::endl + << " constructors." << ::std::endl; + + os << "--generate-merge Generate merge() functions." << ::std::endl; + + os << "--generate-description Generate the option description list that can be" << ::std::endl + << " examined at runtime." << ::std::endl; + + os << "--generate-file-scanner Generate the argv_file_scanner implementation." << ::std::endl; + + os << "--generate-vector-scanner Generate the vector_scanner implementation." << ::std::endl; + + os << "--generate-group-scanner Generate the group_scanner implementation." << ::std::endl; + + os << "--suppress-inline Generate all functions non-inline." << ::std::endl; + + os << "--suppress-cli Do not generate the CLI support types (scanners," << ::std::endl + << " parser, etc)." << ::std::endl; + + os << "--cli-namespace <ns> Generate the CLI support types in the <ns>" << ::std::endl + << " namespace (cli by default)." << ::std::endl; + + os << "--ostream-type <type> Output stream type instead of the default" << ::std::endl + << " std::ostream that should be used to print usage" << ::std::endl + << " and exception information." << ::std::endl; + + os << "--generate-cxx Generate C++ code." << ::std::endl; + + os << "--generate-man Generate documentation in the man page format." << ::std::endl; + + os << "--generate-html Generate documentation in the HTML format." << ::std::endl; + + os << "--generate-txt Generate documentation in the plain text format," << ::std::endl + << " similar to usage." << ::std::endl; + + os << "--stdout Write output to STDOUT instead of a file." << ::std::endl; + + os << "--suppress-undocumented Suppress the generation of documentation entries" << ::std::endl + << " for undocumented options." << ::std::endl; + + os << "--suppress-usage Suppress the generation of the usage printing" << ::std::endl + << " code." << ::std::endl; + + os << "--long-usage If no short documentation string is provided, use" << ::std::endl + << " the complete long documentation string in usage." << ::std::endl; + + os << "--short-usage If specified together with --long-usage, generate" << ::std::endl + << " both short and long usage versions." << ::std::endl; + + os << "--page-usage <name> Generate the combined usage printing code for the" << ::std::endl + << " entire page." << ::std::endl; + + os << "--option-length <len> Indent option descriptions <len> characters when" << ::std::endl + << " printing usage." << ::std::endl; + + os << "--ansi-color Use ANSI color escape sequences when printing" << ::std::endl + << " usage." << ::std::endl; + + os << "--exclude-base Exclude base class information from usage and" << ::std::endl + << " documentation." << ::std::endl; + + os << "--include-base-last Include base class information after derived for" << ::std::endl + << " usage and documentation." << ::std::endl; + + os << "--class-doc <name>=<kind> Specify the documentation <kind> that should be" << ::std::endl + << " used for the options class <name>." << ::std::endl; + + os << "--class <name> Generate the man page, HTML, or text documentation" << ::std::endl + << " only for the options class <name>." << ::std::endl; + + os << "--docvar|-v <name>=<val> Set documentation variable <name> to the value" << ::std::endl + << " <val>." << ::std::endl; + + os << "--link-regex <regex> Add <regex> to the list of regular expressions" << ::std::endl + << " used to transform link targets in the generated" << ::std::endl + << " documentation." << ::std::endl; + + os << "--link-regex-trace Trace the process of applying regular expressions" << ::std::endl + << " specified with the --link-regex option." << ::std::endl; + + os << "--html-heading-map <c>=<h> Map CLI heading <c> (valid values: 'H', '0', '1'," << ::std::endl + << " 'h', and '2') to HTML heading <h> (for example," << ::std::endl + << " 'h1', 'h2', etc)." << ::std::endl; + + os << "--omit-link-check Don't check that local fragment link references" << ::std::endl + << " (\\l{#ref ...}) resolve to ids." << ::std::endl; + + os << "--hxx-prologue <text> Insert <text> at the beginning of the generated" << ::std::endl + << " C++ header file." << ::std::endl; + + os << "--ixx-prologue <text> Insert <text> at the beginning of the generated" << ::std::endl + << " C++ inline file." << ::std::endl; + + os << "--cxx-prologue <text> Insert <text> at the beginning of the generated" << ::std::endl + << " C++ source file." << ::std::endl; + + os << "--man-prologue <text> Insert <text> at the beginning of the generated" << ::std::endl + << " man page file." << ::std::endl; + + os << "--html-prologue <text> Insert <text> at the beginning of the generated" << ::std::endl + << " HTML file." << ::std::endl; + + os << "--txt-prologue <text> Insert <text> at the beginning of the generated" << ::std::endl + << " text file." << ::std::endl; + + os << "--hxx-epilogue <text> Insert <text> at the end of the generated C++" << ::std::endl + << " header file." << ::std::endl; + + os << "--ixx-epilogue <text> Insert <text> at the end of the generated C++" << ::std::endl + << " inline file." << ::std::endl; + + os << "--cxx-epilogue <text> Insert <text> at the end of the generated C++" << ::std::endl + << " source file." << ::std::endl; + + os << "--man-epilogue <text> Insert <text> at the end of the generated man page" << ::std::endl + << " file." << ::std::endl; + + os << "--html-epilogue <text> Insert <text> at the end of the generated HTML" << ::std::endl + << " file." << ::std::endl; + + os << "--txt-epilogue <text> Insert <text> at the end of the generated text" << ::std::endl + << " file." << ::std::endl; + + os << "--hxx-prologue-file <file> Insert the content of <file> at the beginning of" << ::std::endl + << " the generated C++ header file." << ::std::endl; + + os << "--ixx-prologue-file <file> Insert the content of <file> at the beginning of" << ::std::endl + << " the generated C++ inline file." << ::std::endl; + + os << "--cxx-prologue-file <file> Insert the content of <file> at the beginning of" << ::std::endl + << " the generated C++ source file." << ::std::endl; + + os << "--man-prologue-file <file> Insert the content of <file> at the beginning of" << ::std::endl + << " the generated man page file." << ::std::endl; + + os << "--html-prologue-file <file> Insert the content of <file> at the beginning of" << ::std::endl + << " the generated HTML file." << ::std::endl; + + os << "--txt-prologue-file <file> Insert the content of <file> at the beginning of" << ::std::endl + << " the generated text file." << ::std::endl; + + os << "--hxx-epilogue-file <file> Insert the content of <file> at the end of the" << ::std::endl + << " generated C++ header file." << ::std::endl; + + os << "--ixx-epilogue-file <file> Insert the content of <file> at the end of the" << ::std::endl + << " generated C++ inline file." << ::std::endl; + + os << "--cxx-epilogue-file <file> Insert the content of <file> at the end of the" << ::std::endl + << " generated C++ source file." << ::std::endl; + + os << "--man-epilogue-file <file> Insert the content of <file> at the end of the" << ::std::endl + << " generated man page file." << ::std::endl; + + os << "--html-epilogue-file <file> Insert the content of <file> at the end of the" << ::std::endl + << " generated HTML file." << ::std::endl; + + os << "--txt-epilogue-file <file> Insert the content of <file> at the end of the" << ::std::endl + << " generated text file." << ::std::endl; + + os << "--output-prefix <prefix> Add <prefix> at the beginning of the generated" << ::std::endl + << " output file name(s)." << ::std::endl; + + os << "--output-suffix <suffix> Add <suffix> at the end of the generated output" << ::std::endl + << " file name(s)." << ::std::endl; + + os << "--hxx-suffix <suffix> Use <suffix> instead of the default .hxx to" << ::std::endl + << " construct the name of the generated header file." << ::std::endl; + + os << "--ixx-suffix <suffix> Use <suffix> instead of the default .ixx to" << ::std::endl + << " construct the name of the generated inline file." << ::std::endl; + + os << "--cxx-suffix <suffix> Use <suffix> instead of the default .cxx to" << ::std::endl + << " construct the name of the generated source file." << ::std::endl; + + os << "--man-suffix <suffix> Use <suffix> instead of the default .1 to" << ::std::endl + << " construct the name of the generated man page file." << ::std::endl; + + os << "--html-suffix <suffix> Use <suffix> instead of the default .html to" << ::std::endl + << " construct the name of the generated HTML file." << ::std::endl; + + os << "--txt-suffix <suffix> Use <suffix> instead of the default .txt to" << ::std::endl + << " construct the name of the generated text file." << ::std::endl; + + os << "--option-prefix <prefix> Use <prefix> instead of the default '-' as an" << ::std::endl + << " option prefix." << ::std::endl; + + os << "--option-separator <sep> Use <sep> instead of the default '--' as an" << ::std::endl + << " optional separator between options and arguments." << ::std::endl; + + os << "--keep-separator Leave the option separator in the scanner." << ::std::endl; + + os << "--no-combined-flags Disable support for combining multiple" << ::std::endl + << " single-character flags into a single argument (the" << ::std::endl + << " -xyz form that is equivalent to -x -y -z)." << ::std::endl; + + os << "--no-combined-values Disable support for combining an option and its" << ::std::endl + << " value into a single argument with the assignment" << ::std::endl + << " sign (the option=value form)." << ::std::endl; + + os << "--include-with-brackets Use angle brackets (<>) instead of quotes (\"\") in" << ::std::endl + << " the generated #include directives." << ::std::endl; + + os << "--include-prefix <prefix> Add <prefix> to the generated #include directive" << ::std::endl + << " paths." << ::std::endl; + + os << "--guard-prefix <prefix> Add <prefix> to the generated header inclusion" << ::std::endl + << " guards." << ::std::endl; + + os << "--reserved-name <name>=<rep> Add <name> with an optional <rep> replacement to" << ::std::endl + << " the list of names that should not be used as" << ::std::endl + << " identifiers." << ::std::endl; + + os << "--options-file <file> Read additional options from <file>." << ::std::endl; + + p = ::cli::usage_para::option; + + return p; +} + +typedef +std::map<std::string, void (*) (options&, ::cli::scanner&)> +_cli_options_map; + +static _cli_options_map _cli_options_map_; + +struct _cli_options_map_init +{ + _cli_options_map_init () + { + _cli_options_map_["--build2-metadata"] = + &::cli::thunk< options, bool, &options::build2_metadata_ >; + _cli_options_map_["--help"] = + &::cli::thunk< options, bool, &options::help_ >; + _cli_options_map_["--version"] = + &::cli::thunk< options, bool, &options::version_ >; + _cli_options_map_["--include-path"] = + &::cli::thunk< options, std::vector<std::string>, &options::include_path_, + &options::include_path_specified_ >; + _cli_options_map_["-I"] = + &::cli::thunk< options, std::vector<std::string>, &options::include_path_, + &options::include_path_specified_ >; + _cli_options_map_["--output-dir"] = + &::cli::thunk< options, std::string, &options::output_dir_, + &options::output_dir_specified_ >; + _cli_options_map_["-o"] = + &::cli::thunk< options, std::string, &options::output_dir_, + &options::output_dir_specified_ >; + _cli_options_map_["--std"] = + &::cli::thunk< options, cxx_version, &options::std_, + &options::std_specified_ >; + _cli_options_map_["--generate-modifier"] = + &::cli::thunk< options, bool, &options::generate_modifier_ >; + _cli_options_map_["--generate-specifier"] = + &::cli::thunk< options, bool, &options::generate_specifier_ >; + _cli_options_map_["--generate-parse"] = + &::cli::thunk< options, bool, &options::generate_parse_ >; + _cli_options_map_["--generate-merge"] = + &::cli::thunk< options, bool, &options::generate_merge_ >; + _cli_options_map_["--generate-description"] = + &::cli::thunk< options, bool, &options::generate_description_ >; + _cli_options_map_["--generate-file-scanner"] = + &::cli::thunk< options, bool, &options::generate_file_scanner_ >; + _cli_options_map_["--generate-vector-scanner"] = + &::cli::thunk< options, bool, &options::generate_vector_scanner_ >; + _cli_options_map_["--generate-group-scanner"] = + &::cli::thunk< options, bool, &options::generate_group_scanner_ >; + _cli_options_map_["--suppress-inline"] = + &::cli::thunk< options, bool, &options::suppress_inline_ >; + _cli_options_map_["--suppress-cli"] = + &::cli::thunk< options, bool, &options::suppress_cli_ >; + _cli_options_map_["--cli-namespace"] = + &::cli::thunk< options, std::string, &options::cli_namespace_, + &options::cli_namespace_specified_ >; + _cli_options_map_["--ostream-type"] = + &::cli::thunk< options, std::string, &options::ostream_type_, + &options::ostream_type_specified_ >; + _cli_options_map_["--generate-cxx"] = + &::cli::thunk< options, bool, &options::generate_cxx_ >; + _cli_options_map_["--generate-man"] = + &::cli::thunk< options, bool, &options::generate_man_ >; + _cli_options_map_["--generate-html"] = + &::cli::thunk< options, bool, &options::generate_html_ >; + _cli_options_map_["--generate-txt"] = + &::cli::thunk< options, bool, &options::generate_txt_ >; + _cli_options_map_["--stdout"] = + &::cli::thunk< options, bool, &options::stdout__ >; + _cli_options_map_["--suppress-undocumented"] = + &::cli::thunk< options, bool, &options::suppress_undocumented_ >; + _cli_options_map_["--suppress-usage"] = + &::cli::thunk< options, bool, &options::suppress_usage_ >; + _cli_options_map_["--long-usage"] = + &::cli::thunk< options, bool, &options::long_usage_ >; + _cli_options_map_["--short-usage"] = + &::cli::thunk< options, bool, &options::short_usage_ >; + _cli_options_map_["--page-usage"] = + &::cli::thunk< options, std::string, &options::page_usage_, + &options::page_usage_specified_ >; + _cli_options_map_["--option-length"] = + &::cli::thunk< options, std::size_t, &options::option_length_, + &options::option_length_specified_ >; + _cli_options_map_["--ansi-color"] = + &::cli::thunk< options, bool, &options::ansi_color_ >; + _cli_options_map_["--exclude-base"] = + &::cli::thunk< options, bool, &options::exclude_base_ >; + _cli_options_map_["--include-base-last"] = + &::cli::thunk< options, bool, &options::include_base_last_ >; + _cli_options_map_["--class-doc"] = + &::cli::thunk< options, std::map<std::string, std::string>, &options::class_doc_, + &options::class_doc_specified_ >; + _cli_options_map_["--class"] = + &::cli::thunk< options, std::vector<std::string>, &options::class__, + &options::class__specified_ >; + _cli_options_map_["--docvar"] = + &::cli::thunk< options, std::map<std::string, std::string>, &options::docvar_, + &options::docvar_specified_ >; + _cli_options_map_["-v"] = + &::cli::thunk< options, std::map<std::string, std::string>, &options::docvar_, + &options::docvar_specified_ >; + _cli_options_map_["--link-regex"] = + &::cli::thunk< options, std::vector<std::string>, &options::link_regex_, + &options::link_regex_specified_ >; + _cli_options_map_["--link-regex-trace"] = + &::cli::thunk< options, bool, &options::link_regex_trace_ >; + _cli_options_map_["--html-heading-map"] = + &::cli::thunk< options, std::map<char, std::string>, &options::html_heading_map_, + &options::html_heading_map_specified_ >; + _cli_options_map_["--omit-link-check"] = + &::cli::thunk< options, bool, &options::omit_link_check_ >; + _cli_options_map_["--hxx-prologue"] = + &::cli::thunk< options, std::vector<std::string>, &options::hxx_prologue_, + &options::hxx_prologue_specified_ >; + _cli_options_map_["--ixx-prologue"] = + &::cli::thunk< options, std::vector<std::string>, &options::ixx_prologue_, + &options::ixx_prologue_specified_ >; + _cli_options_map_["--cxx-prologue"] = + &::cli::thunk< options, std::vector<std::string>, &options::cxx_prologue_, + &options::cxx_prologue_specified_ >; + _cli_options_map_["--man-prologue"] = + &::cli::thunk< options, std::vector<std::string>, &options::man_prologue_, + &options::man_prologue_specified_ >; + _cli_options_map_["--html-prologue"] = + &::cli::thunk< options, std::vector<std::string>, &options::html_prologue_, + &options::html_prologue_specified_ >; + _cli_options_map_["--txt-prologue"] = + &::cli::thunk< options, std::vector<std::string>, &options::txt_prologue_, + &options::txt_prologue_specified_ >; + _cli_options_map_["--hxx-epilogue"] = + &::cli::thunk< options, std::vector<std::string>, &options::hxx_epilogue_, + &options::hxx_epilogue_specified_ >; + _cli_options_map_["--ixx-epilogue"] = + &::cli::thunk< options, std::vector<std::string>, &options::ixx_epilogue_, + &options::ixx_epilogue_specified_ >; + _cli_options_map_["--cxx-epilogue"] = + &::cli::thunk< options, std::vector<std::string>, &options::cxx_epilogue_, + &options::cxx_epilogue_specified_ >; + _cli_options_map_["--man-epilogue"] = + &::cli::thunk< options, std::vector<std::string>, &options::man_epilogue_, + &options::man_epilogue_specified_ >; + _cli_options_map_["--html-epilogue"] = + &::cli::thunk< options, std::vector<std::string>, &options::html_epilogue_, + &options::html_epilogue_specified_ >; + _cli_options_map_["--txt-epilogue"] = + &::cli::thunk< options, std::vector<std::string>, &options::txt_epilogue_, + &options::txt_epilogue_specified_ >; + _cli_options_map_["--hxx-prologue-file"] = + &::cli::thunk< options, std::string, &options::hxx_prologue_file_, + &options::hxx_prologue_file_specified_ >; + _cli_options_map_["--ixx-prologue-file"] = + &::cli::thunk< options, std::string, &options::ixx_prologue_file_, + &options::ixx_prologue_file_specified_ >; + _cli_options_map_["--cxx-prologue-file"] = + &::cli::thunk< options, std::string, &options::cxx_prologue_file_, + &options::cxx_prologue_file_specified_ >; + _cli_options_map_["--man-prologue-file"] = + &::cli::thunk< options, std::string, &options::man_prologue_file_, + &options::man_prologue_file_specified_ >; + _cli_options_map_["--html-prologue-file"] = + &::cli::thunk< options, std::string, &options::html_prologue_file_, + &options::html_prologue_file_specified_ >; + _cli_options_map_["--txt-prologue-file"] = + &::cli::thunk< options, std::string, &options::txt_prologue_file_, + &options::txt_prologue_file_specified_ >; + _cli_options_map_["--hxx-epilogue-file"] = + &::cli::thunk< options, std::string, &options::hxx_epilogue_file_, + &options::hxx_epilogue_file_specified_ >; + _cli_options_map_["--ixx-epilogue-file"] = + &::cli::thunk< options, std::string, &options::ixx_epilogue_file_, + &options::ixx_epilogue_file_specified_ >; + _cli_options_map_["--cxx-epilogue-file"] = + &::cli::thunk< options, std::string, &options::cxx_epilogue_file_, + &options::cxx_epilogue_file_specified_ >; + _cli_options_map_["--man-epilogue-file"] = + &::cli::thunk< options, std::string, &options::man_epilogue_file_, + &options::man_epilogue_file_specified_ >; + _cli_options_map_["--html-epilogue-file"] = + &::cli::thunk< options, std::string, &options::html_epilogue_file_, + &options::html_epilogue_file_specified_ >; + _cli_options_map_["--txt-epilogue-file"] = + &::cli::thunk< options, std::string, &options::txt_epilogue_file_, + &options::txt_epilogue_file_specified_ >; + _cli_options_map_["--output-prefix"] = + &::cli::thunk< options, std::string, &options::output_prefix_, + &options::output_prefix_specified_ >; + _cli_options_map_["--output-suffix"] = + &::cli::thunk< options, std::string, &options::output_suffix_, + &options::output_suffix_specified_ >; + _cli_options_map_["--hxx-suffix"] = + &::cli::thunk< options, std::string, &options::hxx_suffix_, + &options::hxx_suffix_specified_ >; + _cli_options_map_["--ixx-suffix"] = + &::cli::thunk< options, std::string, &options::ixx_suffix_, + &options::ixx_suffix_specified_ >; + _cli_options_map_["--cxx-suffix"] = + &::cli::thunk< options, std::string, &options::cxx_suffix_, + &options::cxx_suffix_specified_ >; + _cli_options_map_["--man-suffix"] = + &::cli::thunk< options, std::string, &options::man_suffix_, + &options::man_suffix_specified_ >; + _cli_options_map_["--html-suffix"] = + &::cli::thunk< options, std::string, &options::html_suffix_, + &options::html_suffix_specified_ >; + _cli_options_map_["--txt-suffix"] = + &::cli::thunk< options, std::string, &options::txt_suffix_, + &options::txt_suffix_specified_ >; + _cli_options_map_["--option-prefix"] = + &::cli::thunk< options, std::string, &options::option_prefix_, + &options::option_prefix_specified_ >; + _cli_options_map_["--option-separator"] = + &::cli::thunk< options, std::string, &options::option_separator_, + &options::option_separator_specified_ >; + _cli_options_map_["--keep-separator"] = + &::cli::thunk< options, bool, &options::keep_separator_ >; + _cli_options_map_["--no-combined-flags"] = + &::cli::thunk< options, bool, &options::no_combined_flags_ >; + _cli_options_map_["--no-combined-values"] = + &::cli::thunk< options, bool, &options::no_combined_values_ >; + _cli_options_map_["--include-with-brackets"] = + &::cli::thunk< options, bool, &options::include_with_brackets_ >; + _cli_options_map_["--include-prefix"] = + &::cli::thunk< options, std::string, &options::include_prefix_, + &options::include_prefix_specified_ >; + _cli_options_map_["--guard-prefix"] = + &::cli::thunk< options, std::string, &options::guard_prefix_, + &options::guard_prefix_specified_ >; + _cli_options_map_["--reserved-name"] = + &::cli::thunk< options, std::map<std::string, std::string>, &options::reserved_name_, + &options::reserved_name_specified_ >; + _cli_options_map_["--options-file"] = + &::cli::thunk< options, std::string, &options::options_file_, + &options::options_file_specified_ >; + } +}; + +static _cli_options_map_init _cli_options_map_init_; + +bool options:: +_parse (const char* o, ::cli::scanner& s) +{ + _cli_options_map::const_iterator i (_cli_options_map_.find (o)); + + if (i != _cli_options_map_.end ()) + { + (*(i->second)) (*this, s); + return true; + } + + return false; +} + +bool options:: +_parse (::cli::scanner& s, + ::cli::unknown_mode opt_mode, + ::cli::unknown_mode arg_mode) +{ + // Can't skip combined flags (--no-combined-flags). + // + assert (opt_mode != ::cli::unknown_mode::skip); + + bool r = false; + bool opt = true; + + while (s.more ()) + { + const char* o = s.peek (); + + if (std::strcmp (o, "--") == 0) + { + opt = false; + s.skip (); + r = true; + continue; + } + + if (opt) + { + if (_parse (o, s)) + { + r = true; + continue; + } + + if (std::strncmp (o, "-", 1) == 0 && o[1] != '\0') + { + // Handle combined option values. + // + std::string co; + if (const char* v = std::strchr (o, '=')) + { + co.assign (o, 0, v - o); + ++v; + + int ac (2); + char* av[] = + { + const_cast<char*> (co.c_str ()), + const_cast<char*> (v) + }; + + ::cli::argv_scanner ns (0, ac, av); + + if (_parse (co.c_str (), ns)) + { + // Parsed the option but not its value? + // + if (ns.end () != 2) + throw ::cli::invalid_value (co, v); + + s.next (); + r = true; + continue; + } + else + { + // Set the unknown option and fall through. + // + o = co.c_str (); + } + } + + // Handle combined flags. + // + char cf[3]; + { + const char* p = o + 1; + for (; *p != '\0'; ++p) + { + if (!((*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z') || + (*p >= '0' && *p <= '9'))) + break; + } + + if (*p == '\0') + { + for (p = o + 1; *p != '\0'; ++p) + { + std::strcpy (cf, "-"); + cf[1] = *p; + cf[2] = '\0'; + + int ac (1); + char* av[] = + { + cf + }; + + ::cli::argv_scanner ns (0, ac, av); + + if (!_parse (cf, ns)) + break; + } + + if (*p == '\0') + { + // All handled. + // + s.next (); + r = true; + continue; + } + else + { + // Set the unknown option and fall through. + // + o = cf; + } + } + } + + switch (opt_mode) + { + case ::cli::unknown_mode::skip: + { + s.skip (); + r = true; + continue; + } + case ::cli::unknown_mode::stop: + { + break; + } + case ::cli::unknown_mode::fail: + { + throw ::cli::unknown_option (o); + } + } + + break; + } + } + + switch (arg_mode) + { + case ::cli::unknown_mode::skip: + { + s.skip (); + r = true; + continue; + } + case ::cli::unknown_mode::stop: + { + break; + } + case ::cli::unknown_mode::fail: + { + throw ::cli::unknown_argument (o); + } + } + + break; + } + + return r; +} + +// Begin epilogue. +// +// +// End epilogue. + diff --git a/cli/cli/options.hxx b/cli/cli/options.hxx new file mode 100644 index 0000000..b253092 --- /dev/null +++ b/cli/cli/options.hxx @@ -0,0 +1,1632 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +#ifndef CLI_OPTIONS_HXX +#define CLI_OPTIONS_HXX + +// Begin prologue. +// +// +// End prologue. + +#include <list> +#include <deque> +#include <iosfwd> +#include <string> +#include <cstddef> +#include <exception> + +#ifndef CLI_POTENTIALLY_UNUSED +# if defined(_MSC_VER) || defined(__xlC__) +# define CLI_POTENTIALLY_UNUSED(x) (void*)&x +# else +# define CLI_POTENTIALLY_UNUSED(x) (void)x +# endif +#endif + +namespace cli +{ + class usage_para + { + public: + enum value + { + none, + text, + option + }; + + usage_para (value); + + operator value () const + { + return v_; + } + + private: + value v_; + }; + + class unknown_mode + { + public: + enum value + { + skip, + stop, + fail + }; + + unknown_mode (value); + + operator value () const + { + return v_; + } + + private: + value v_; + }; + + // Exceptions. + // + + class exception: public std::exception + { + public: + virtual void + print (::std::ostream&) const = 0; + }; + + ::std::ostream& + operator<< (::std::ostream&, const exception&); + + class unknown_option: public exception + { + public: + virtual + ~unknown_option () throw (); + + unknown_option (const std::string& option); + + const std::string& + option () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string option_; + }; + + class unknown_argument: public exception + { + public: + virtual + ~unknown_argument () throw (); + + unknown_argument (const std::string& argument); + + const std::string& + argument () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string argument_; + }; + + class missing_value: public exception + { + public: + virtual + ~missing_value () throw (); + + missing_value (const std::string& option); + + const std::string& + option () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string option_; + }; + + class invalid_value: public exception + { + public: + virtual + ~invalid_value () throw (); + + invalid_value (const std::string& option, + const std::string& value, + const std::string& message = std::string ()); + + const std::string& + option () const; + + const std::string& + value () const; + + const std::string& + message () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string option_; + std::string value_; + std::string message_; + }; + + class eos_reached: public exception + { + public: + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + }; + + class file_io_failure: public exception + { + public: + virtual + ~file_io_failure () throw (); + + file_io_failure (const std::string& file); + + const std::string& + file () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string file_; + }; + + class unmatched_quote: public exception + { + public: + virtual + ~unmatched_quote () throw (); + + unmatched_quote (const std::string& argument); + + const std::string& + argument () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string argument_; + }; + + // Command line argument scanner interface. + // + // The values returned by next() are guaranteed to be valid + // for the two previous arguments up until a call to a third + // peek() or next(). + // + class scanner + { + public: + virtual + ~scanner (); + + virtual bool + more () = 0; + + virtual const char* + peek () = 0; + + virtual const char* + next () = 0; + + virtual void + skip () = 0; + }; + + class argv_scanner: public scanner + { + public: + argv_scanner (int& argc, char** argv, bool erase = false); + argv_scanner (int start, int& argc, char** argv, bool erase = false); + + int + end () const; + + virtual bool + more (); + + virtual const char* + peek (); + + virtual const char* + next (); + + virtual void + skip (); + + private: + int i_; + int& argc_; + char** argv_; + bool erase_; + }; + + class argv_file_scanner: public argv_scanner + { + public: + argv_file_scanner (int& argc, + char** argv, + const std::string& option, + bool erase = false); + + argv_file_scanner (int start, + int& argc, + char** argv, + const std::string& option, + bool erase = false); + + argv_file_scanner (const std::string& file, + const std::string& option); + + struct option_info + { + // If search_func is not NULL, it is called, with the arg + // value as the second argument, to locate the options file. + // If it returns an empty string, then the file is ignored. + // + const char* option; + std::string (*search_func) (const char*, void* arg); + void* arg; + }; + + argv_file_scanner (int& argc, + char** argv, + const option_info* options, + std::size_t options_count, + bool erase = false); + + argv_file_scanner (int start, + int& argc, + char** argv, + const option_info* options, + std::size_t options_count, + bool erase = false); + + argv_file_scanner (const std::string& file, + const option_info* options = 0, + std::size_t options_count = 0); + + virtual bool + more (); + + virtual const char* + peek (); + + virtual const char* + next (); + + virtual void + skip (); + + // Return the file path if the peeked at argument came from a file and + // the empty string otherwise. The reference is guaranteed to be valid + // till the end of the scanner lifetime. + // + const std::string& + peek_file (); + + // Return the 1-based line number if the peeked at argument came from + // a file and zero otherwise. + // + std::size_t + peek_line (); + + private: + const option_info* + find (const char*) const; + + void + load (const std::string& file); + + typedef argv_scanner base; + + const std::string option_; + option_info option_info_; + const option_info* options_; + std::size_t options_count_; + + struct arg + { + std::string value; + const std::string* file; + std::size_t line; + }; + + std::deque<arg> args_; + std::list<std::string> files_; + + // Circular buffer of two arguments. + // + std::string hold_[2]; + std::size_t i_; + + bool skip_; + + static int zero_argc_; + static std::string empty_string_; + }; + + template <typename X> + struct parser; +} + +#include <map> + +#include <string> + +#include <vector> + +#include <cstddef> + +#include <cli/option-types.hxx> + +class options +{ + public: + options (); + + options (int& argc, + char** argv, + bool erase = false, + ::cli::unknown_mode option = ::cli::unknown_mode::fail, + ::cli::unknown_mode argument = ::cli::unknown_mode::stop); + + options (int start, + int& argc, + char** argv, + bool erase = false, + ::cli::unknown_mode option = ::cli::unknown_mode::fail, + ::cli::unknown_mode argument = ::cli::unknown_mode::stop); + + options (int& argc, + char** argv, + int& end, + bool erase = false, + ::cli::unknown_mode option = ::cli::unknown_mode::fail, + ::cli::unknown_mode argument = ::cli::unknown_mode::stop); + + options (int start, + int& argc, + char** argv, + int& end, + bool erase = false, + ::cli::unknown_mode option = ::cli::unknown_mode::fail, + ::cli::unknown_mode argument = ::cli::unknown_mode::stop); + + options (::cli::scanner&, + ::cli::unknown_mode option = ::cli::unknown_mode::fail, + ::cli::unknown_mode argument = ::cli::unknown_mode::stop); + + // Option accessors and modifiers. + // + const bool& + build2_metadata () const; + + bool& + build2_metadata (); + + void + build2_metadata (const bool&); + + const bool& + help () const; + + bool& + help (); + + void + help (const bool&); + + const bool& + version () const; + + bool& + version (); + + void + version (const bool&); + + const std::vector<std::string>& + include_path () const; + + std::vector<std::string>& + include_path (); + + void + include_path (const std::vector<std::string>&); + + bool + include_path_specified () const; + + void + include_path_specified (bool); + + const std::string& + output_dir () const; + + std::string& + output_dir (); + + void + output_dir (const std::string&); + + bool + output_dir_specified () const; + + void + output_dir_specified (bool); + + const cxx_version& + std () const; + + cxx_version& + std (); + + void + std (const cxx_version&); + + bool + std_specified () const; + + void + std_specified (bool); + + const bool& + generate_modifier () const; + + bool& + generate_modifier (); + + void + generate_modifier (const bool&); + + const bool& + generate_specifier () const; + + bool& + generate_specifier (); + + void + generate_specifier (const bool&); + + const bool& + generate_parse () const; + + bool& + generate_parse (); + + void + generate_parse (const bool&); + + const bool& + generate_merge () const; + + bool& + generate_merge (); + + void + generate_merge (const bool&); + + const bool& + generate_description () const; + + bool& + generate_description (); + + void + generate_description (const bool&); + + const bool& + generate_file_scanner () const; + + bool& + generate_file_scanner (); + + void + generate_file_scanner (const bool&); + + const bool& + generate_vector_scanner () const; + + bool& + generate_vector_scanner (); + + void + generate_vector_scanner (const bool&); + + const bool& + generate_group_scanner () const; + + bool& + generate_group_scanner (); + + void + generate_group_scanner (const bool&); + + const bool& + suppress_inline () const; + + bool& + suppress_inline (); + + void + suppress_inline (const bool&); + + const bool& + suppress_cli () const; + + bool& + suppress_cli (); + + void + suppress_cli (const bool&); + + const std::string& + cli_namespace () const; + + std::string& + cli_namespace (); + + void + cli_namespace (const std::string&); + + bool + cli_namespace_specified () const; + + void + cli_namespace_specified (bool); + + const std::string& + ostream_type () const; + + std::string& + ostream_type (); + + void + ostream_type (const std::string&); + + bool + ostream_type_specified () const; + + void + ostream_type_specified (bool); + + const bool& + generate_cxx () const; + + bool& + generate_cxx (); + + void + generate_cxx (const bool&); + + const bool& + generate_man () const; + + bool& + generate_man (); + + void + generate_man (const bool&); + + const bool& + generate_html () const; + + bool& + generate_html (); + + void + generate_html (const bool&); + + const bool& + generate_txt () const; + + bool& + generate_txt (); + + void + generate_txt (const bool&); + + const bool& + stdout_ () const; + + bool& + stdout_ (); + + void + stdout_ (const bool&); + + const bool& + suppress_undocumented () const; + + bool& + suppress_undocumented (); + + void + suppress_undocumented (const bool&); + + const bool& + suppress_usage () const; + + bool& + suppress_usage (); + + void + suppress_usage (const bool&); + + const bool& + long_usage () const; + + bool& + long_usage (); + + void + long_usage (const bool&); + + const bool& + short_usage () const; + + bool& + short_usage (); + + void + short_usage (const bool&); + + const std::string& + page_usage () const; + + std::string& + page_usage (); + + void + page_usage (const std::string&); + + bool + page_usage_specified () const; + + void + page_usage_specified (bool); + + const std::size_t& + option_length () const; + + std::size_t& + option_length (); + + void + option_length (const std::size_t&); + + bool + option_length_specified () const; + + void + option_length_specified (bool); + + const bool& + ansi_color () const; + + bool& + ansi_color (); + + void + ansi_color (const bool&); + + const bool& + exclude_base () const; + + bool& + exclude_base (); + + void + exclude_base (const bool&); + + const bool& + include_base_last () const; + + bool& + include_base_last (); + + void + include_base_last (const bool&); + + const std::map<std::string, std::string>& + class_doc () const; + + std::map<std::string, std::string>& + class_doc (); + + void + class_doc (const std::map<std::string, std::string>&); + + bool + class_doc_specified () const; + + void + class_doc_specified (bool); + + const std::vector<std::string>& + class_ () const; + + std::vector<std::string>& + class_ (); + + void + class_ (const std::vector<std::string>&); + + bool + class__specified () const; + + void + class__specified (bool); + + const std::map<std::string, std::string>& + docvar () const; + + std::map<std::string, std::string>& + docvar (); + + void + docvar (const std::map<std::string, std::string>&); + + bool + docvar_specified () const; + + void + docvar_specified (bool); + + const std::vector<std::string>& + link_regex () const; + + std::vector<std::string>& + link_regex (); + + void + link_regex (const std::vector<std::string>&); + + bool + link_regex_specified () const; + + void + link_regex_specified (bool); + + const bool& + link_regex_trace () const; + + bool& + link_regex_trace (); + + void + link_regex_trace (const bool&); + + const std::map<char, std::string>& + html_heading_map () const; + + std::map<char, std::string>& + html_heading_map (); + + void + html_heading_map (const std::map<char, std::string>&); + + bool + html_heading_map_specified () const; + + void + html_heading_map_specified (bool); + + const bool& + omit_link_check () const; + + bool& + omit_link_check (); + + void + omit_link_check (const bool&); + + const std::vector<std::string>& + hxx_prologue () const; + + std::vector<std::string>& + hxx_prologue (); + + void + hxx_prologue (const std::vector<std::string>&); + + bool + hxx_prologue_specified () const; + + void + hxx_prologue_specified (bool); + + const std::vector<std::string>& + ixx_prologue () const; + + std::vector<std::string>& + ixx_prologue (); + + void + ixx_prologue (const std::vector<std::string>&); + + bool + ixx_prologue_specified () const; + + void + ixx_prologue_specified (bool); + + const std::vector<std::string>& + cxx_prologue () const; + + std::vector<std::string>& + cxx_prologue (); + + void + cxx_prologue (const std::vector<std::string>&); + + bool + cxx_prologue_specified () const; + + void + cxx_prologue_specified (bool); + + const std::vector<std::string>& + man_prologue () const; + + std::vector<std::string>& + man_prologue (); + + void + man_prologue (const std::vector<std::string>&); + + bool + man_prologue_specified () const; + + void + man_prologue_specified (bool); + + const std::vector<std::string>& + html_prologue () const; + + std::vector<std::string>& + html_prologue (); + + void + html_prologue (const std::vector<std::string>&); + + bool + html_prologue_specified () const; + + void + html_prologue_specified (bool); + + const std::vector<std::string>& + txt_prologue () const; + + std::vector<std::string>& + txt_prologue (); + + void + txt_prologue (const std::vector<std::string>&); + + bool + txt_prologue_specified () const; + + void + txt_prologue_specified (bool); + + const std::vector<std::string>& + hxx_epilogue () const; + + std::vector<std::string>& + hxx_epilogue (); + + void + hxx_epilogue (const std::vector<std::string>&); + + bool + hxx_epilogue_specified () const; + + void + hxx_epilogue_specified (bool); + + const std::vector<std::string>& + ixx_epilogue () const; + + std::vector<std::string>& + ixx_epilogue (); + + void + ixx_epilogue (const std::vector<std::string>&); + + bool + ixx_epilogue_specified () const; + + void + ixx_epilogue_specified (bool); + + const std::vector<std::string>& + cxx_epilogue () const; + + std::vector<std::string>& + cxx_epilogue (); + + void + cxx_epilogue (const std::vector<std::string>&); + + bool + cxx_epilogue_specified () const; + + void + cxx_epilogue_specified (bool); + + const std::vector<std::string>& + man_epilogue () const; + + std::vector<std::string>& + man_epilogue (); + + void + man_epilogue (const std::vector<std::string>&); + + bool + man_epilogue_specified () const; + + void + man_epilogue_specified (bool); + + const std::vector<std::string>& + html_epilogue () const; + + std::vector<std::string>& + html_epilogue (); + + void + html_epilogue (const std::vector<std::string>&); + + bool + html_epilogue_specified () const; + + void + html_epilogue_specified (bool); + + const std::vector<std::string>& + txt_epilogue () const; + + std::vector<std::string>& + txt_epilogue (); + + void + txt_epilogue (const std::vector<std::string>&); + + bool + txt_epilogue_specified () const; + + void + txt_epilogue_specified (bool); + + const std::string& + hxx_prologue_file () const; + + std::string& + hxx_prologue_file (); + + void + hxx_prologue_file (const std::string&); + + bool + hxx_prologue_file_specified () const; + + void + hxx_prologue_file_specified (bool); + + const std::string& + ixx_prologue_file () const; + + std::string& + ixx_prologue_file (); + + void + ixx_prologue_file (const std::string&); + + bool + ixx_prologue_file_specified () const; + + void + ixx_prologue_file_specified (bool); + + const std::string& + cxx_prologue_file () const; + + std::string& + cxx_prologue_file (); + + void + cxx_prologue_file (const std::string&); + + bool + cxx_prologue_file_specified () const; + + void + cxx_prologue_file_specified (bool); + + const std::string& + man_prologue_file () const; + + std::string& + man_prologue_file (); + + void + man_prologue_file (const std::string&); + + bool + man_prologue_file_specified () const; + + void + man_prologue_file_specified (bool); + + const std::string& + html_prologue_file () const; + + std::string& + html_prologue_file (); + + void + html_prologue_file (const std::string&); + + bool + html_prologue_file_specified () const; + + void + html_prologue_file_specified (bool); + + const std::string& + txt_prologue_file () const; + + std::string& + txt_prologue_file (); + + void + txt_prologue_file (const std::string&); + + bool + txt_prologue_file_specified () const; + + void + txt_prologue_file_specified (bool); + + const std::string& + hxx_epilogue_file () const; + + std::string& + hxx_epilogue_file (); + + void + hxx_epilogue_file (const std::string&); + + bool + hxx_epilogue_file_specified () const; + + void + hxx_epilogue_file_specified (bool); + + const std::string& + ixx_epilogue_file () const; + + std::string& + ixx_epilogue_file (); + + void + ixx_epilogue_file (const std::string&); + + bool + ixx_epilogue_file_specified () const; + + void + ixx_epilogue_file_specified (bool); + + const std::string& + cxx_epilogue_file () const; + + std::string& + cxx_epilogue_file (); + + void + cxx_epilogue_file (const std::string&); + + bool + cxx_epilogue_file_specified () const; + + void + cxx_epilogue_file_specified (bool); + + const std::string& + man_epilogue_file () const; + + std::string& + man_epilogue_file (); + + void + man_epilogue_file (const std::string&); + + bool + man_epilogue_file_specified () const; + + void + man_epilogue_file_specified (bool); + + const std::string& + html_epilogue_file () const; + + std::string& + html_epilogue_file (); + + void + html_epilogue_file (const std::string&); + + bool + html_epilogue_file_specified () const; + + void + html_epilogue_file_specified (bool); + + const std::string& + txt_epilogue_file () const; + + std::string& + txt_epilogue_file (); + + void + txt_epilogue_file (const std::string&); + + bool + txt_epilogue_file_specified () const; + + void + txt_epilogue_file_specified (bool); + + const std::string& + output_prefix () const; + + std::string& + output_prefix (); + + void + output_prefix (const std::string&); + + bool + output_prefix_specified () const; + + void + output_prefix_specified (bool); + + const std::string& + output_suffix () const; + + std::string& + output_suffix (); + + void + output_suffix (const std::string&); + + bool + output_suffix_specified () const; + + void + output_suffix_specified (bool); + + const std::string& + hxx_suffix () const; + + std::string& + hxx_suffix (); + + void + hxx_suffix (const std::string&); + + bool + hxx_suffix_specified () const; + + void + hxx_suffix_specified (bool); + + const std::string& + ixx_suffix () const; + + std::string& + ixx_suffix (); + + void + ixx_suffix (const std::string&); + + bool + ixx_suffix_specified () const; + + void + ixx_suffix_specified (bool); + + const std::string& + cxx_suffix () const; + + std::string& + cxx_suffix (); + + void + cxx_suffix (const std::string&); + + bool + cxx_suffix_specified () const; + + void + cxx_suffix_specified (bool); + + const std::string& + man_suffix () const; + + std::string& + man_suffix (); + + void + man_suffix (const std::string&); + + bool + man_suffix_specified () const; + + void + man_suffix_specified (bool); + + const std::string& + html_suffix () const; + + std::string& + html_suffix (); + + void + html_suffix (const std::string&); + + bool + html_suffix_specified () const; + + void + html_suffix_specified (bool); + + const std::string& + txt_suffix () const; + + std::string& + txt_suffix (); + + void + txt_suffix (const std::string&); + + bool + txt_suffix_specified () const; + + void + txt_suffix_specified (bool); + + const std::string& + option_prefix () const; + + std::string& + option_prefix (); + + void + option_prefix (const std::string&); + + bool + option_prefix_specified () const; + + void + option_prefix_specified (bool); + + const std::string& + option_separator () const; + + std::string& + option_separator (); + + void + option_separator (const std::string&); + + bool + option_separator_specified () const; + + void + option_separator_specified (bool); + + const bool& + keep_separator () const; + + bool& + keep_separator (); + + void + keep_separator (const bool&); + + const bool& + no_combined_flags () const; + + bool& + no_combined_flags (); + + void + no_combined_flags (const bool&); + + const bool& + no_combined_values () const; + + bool& + no_combined_values (); + + void + no_combined_values (const bool&); + + const bool& + include_with_brackets () const; + + bool& + include_with_brackets (); + + void + include_with_brackets (const bool&); + + const std::string& + include_prefix () const; + + std::string& + include_prefix (); + + void + include_prefix (const std::string&); + + bool + include_prefix_specified () const; + + void + include_prefix_specified (bool); + + const std::string& + guard_prefix () const; + + std::string& + guard_prefix (); + + void + guard_prefix (const std::string&); + + bool + guard_prefix_specified () const; + + void + guard_prefix_specified (bool); + + const std::map<std::string, std::string>& + reserved_name () const; + + std::map<std::string, std::string>& + reserved_name (); + + void + reserved_name (const std::map<std::string, std::string>&); + + bool + reserved_name_specified () const; + + void + reserved_name_specified (bool); + + const std::string& + options_file () const; + + std::string& + options_file (); + + void + options_file (const std::string&); + + bool + options_file_specified () const; + + void + options_file_specified (bool); + + // Print usage information. + // + static ::cli::usage_para + print_usage (::std::ostream&, + ::cli::usage_para = ::cli::usage_para::none); + + // Implementation details. + // + protected: + bool + _parse (const char*, ::cli::scanner&); + + private: + bool + _parse (::cli::scanner&, + ::cli::unknown_mode option, + ::cli::unknown_mode argument); + + public: + bool build2_metadata_; + bool help_; + bool version_; + std::vector<std::string> include_path_; + bool include_path_specified_; + std::string output_dir_; + bool output_dir_specified_; + cxx_version std_; + bool std_specified_; + bool generate_modifier_; + bool generate_specifier_; + bool generate_parse_; + bool generate_merge_; + bool generate_description_; + bool generate_file_scanner_; + bool generate_vector_scanner_; + bool generate_group_scanner_; + bool suppress_inline_; + bool suppress_cli_; + std::string cli_namespace_; + bool cli_namespace_specified_; + std::string ostream_type_; + bool ostream_type_specified_; + bool generate_cxx_; + bool generate_man_; + bool generate_html_; + bool generate_txt_; + bool stdout__; + bool suppress_undocumented_; + bool suppress_usage_; + bool long_usage_; + bool short_usage_; + std::string page_usage_; + bool page_usage_specified_; + std::size_t option_length_; + bool option_length_specified_; + bool ansi_color_; + bool exclude_base_; + bool include_base_last_; + std::map<std::string, std::string> class_doc_; + bool class_doc_specified_; + std::vector<std::string> class__; + bool class__specified_; + std::map<std::string, std::string> docvar_; + bool docvar_specified_; + std::vector<std::string> link_regex_; + bool link_regex_specified_; + bool link_regex_trace_; + std::map<char, std::string> html_heading_map_; + bool html_heading_map_specified_; + bool omit_link_check_; + std::vector<std::string> hxx_prologue_; + bool hxx_prologue_specified_; + std::vector<std::string> ixx_prologue_; + bool ixx_prologue_specified_; + std::vector<std::string> cxx_prologue_; + bool cxx_prologue_specified_; + std::vector<std::string> man_prologue_; + bool man_prologue_specified_; + std::vector<std::string> html_prologue_; + bool html_prologue_specified_; + std::vector<std::string> txt_prologue_; + bool txt_prologue_specified_; + std::vector<std::string> hxx_epilogue_; + bool hxx_epilogue_specified_; + std::vector<std::string> ixx_epilogue_; + bool ixx_epilogue_specified_; + std::vector<std::string> cxx_epilogue_; + bool cxx_epilogue_specified_; + std::vector<std::string> man_epilogue_; + bool man_epilogue_specified_; + std::vector<std::string> html_epilogue_; + bool html_epilogue_specified_; + std::vector<std::string> txt_epilogue_; + bool txt_epilogue_specified_; + std::string hxx_prologue_file_; + bool hxx_prologue_file_specified_; + std::string ixx_prologue_file_; + bool ixx_prologue_file_specified_; + std::string cxx_prologue_file_; + bool cxx_prologue_file_specified_; + std::string man_prologue_file_; + bool man_prologue_file_specified_; + std::string html_prologue_file_; + bool html_prologue_file_specified_; + std::string txt_prologue_file_; + bool txt_prologue_file_specified_; + std::string hxx_epilogue_file_; + bool hxx_epilogue_file_specified_; + std::string ixx_epilogue_file_; + bool ixx_epilogue_file_specified_; + std::string cxx_epilogue_file_; + bool cxx_epilogue_file_specified_; + std::string man_epilogue_file_; + bool man_epilogue_file_specified_; + std::string html_epilogue_file_; + bool html_epilogue_file_specified_; + std::string txt_epilogue_file_; + bool txt_epilogue_file_specified_; + std::string output_prefix_; + bool output_prefix_specified_; + std::string output_suffix_; + bool output_suffix_specified_; + std::string hxx_suffix_; + bool hxx_suffix_specified_; + std::string ixx_suffix_; + bool ixx_suffix_specified_; + std::string cxx_suffix_; + bool cxx_suffix_specified_; + std::string man_suffix_; + bool man_suffix_specified_; + std::string html_suffix_; + bool html_suffix_specified_; + std::string txt_suffix_; + bool txt_suffix_specified_; + std::string option_prefix_; + bool option_prefix_specified_; + std::string option_separator_; + bool option_separator_specified_; + bool keep_separator_; + bool no_combined_flags_; + bool no_combined_values_; + bool include_with_brackets_; + std::string include_prefix_; + bool include_prefix_specified_; + std::string guard_prefix_; + bool guard_prefix_specified_; + std::map<std::string, std::string> reserved_name_; + bool reserved_name_specified_; + std::string options_file_; + bool options_file_specified_; +}; + +#include <cli/options.ixx> + +// Begin epilogue. +// +// +// End epilogue. + +#endif // CLI_OPTIONS_HXX diff --git a/cli/cli/options.ixx b/cli/cli/options.ixx new file mode 100644 index 0000000..12d40db --- /dev/null +++ b/cli/cli/options.ixx @@ -0,0 +1,2319 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +// Begin prologue. +// +// +// End prologue. + +#include <cassert> + +namespace cli +{ + // usage_para + // + inline usage_para:: + usage_para (value v) + : v_ (v) + { + } + + // unknown_mode + // + inline unknown_mode:: + unknown_mode (value v) + : v_ (v) + { + } + + // exception + // + inline ::std::ostream& + operator<< (::std::ostream& os, const exception& e) + { + e.print (os); + return os; + } + + // unknown_option + // + inline unknown_option:: + unknown_option (const std::string& option) + : option_ (option) + { + } + + inline const std::string& unknown_option:: + option () const + { + return option_; + } + + // unknown_argument + // + inline unknown_argument:: + unknown_argument (const std::string& argument) + : argument_ (argument) + { + } + + inline const std::string& unknown_argument:: + argument () const + { + return argument_; + } + + // missing_value + // + inline missing_value:: + missing_value (const std::string& option) + : option_ (option) + { + } + + inline const std::string& missing_value:: + option () const + { + return option_; + } + + // invalid_value + // + inline invalid_value:: + invalid_value (const std::string& option, + const std::string& value, + const std::string& message) + : option_ (option), + value_ (value), + message_ (message) + { + } + + inline const std::string& invalid_value:: + option () const + { + return option_; + } + + inline const std::string& invalid_value:: + value () const + { + return value_; + } + + inline const std::string& invalid_value:: + message () const + { + return message_; + } + + // file_io_failure + // + inline file_io_failure:: + file_io_failure (const std::string& file) + : file_ (file) + { + } + + inline const std::string& file_io_failure:: + file () const + { + return file_; + } + + // unmatched_quote + // + inline unmatched_quote:: + unmatched_quote (const std::string& argument) + : argument_ (argument) + { + } + + inline const std::string& unmatched_quote:: + argument () const + { + return argument_; + } + + // argv_scanner + // + inline argv_scanner:: + argv_scanner (int& argc, char** argv, bool erase) + : i_ (1), argc_ (argc), argv_ (argv), erase_ (erase) + { + } + + inline argv_scanner:: + argv_scanner (int start, int& argc, char** argv, bool erase) + : i_ (start), argc_ (argc), argv_ (argv), erase_ (erase) + { + } + + inline int argv_scanner:: + end () const + { + return i_; + } + + // argv_file_scanner + // + inline argv_file_scanner:: + argv_file_scanner (int& argc, + char** argv, + const std::string& option, + bool erase) + : argv_scanner (argc, argv, erase), + option_ (option), + options_ (&option_info_), + options_count_ (1), + i_ (1), + skip_ (false) + { + option_info_.option = option_.c_str (); + option_info_.search_func = 0; + } + + inline argv_file_scanner:: + argv_file_scanner (int start, + int& argc, + char** argv, + const std::string& option, + bool erase) + : argv_scanner (start, argc, argv, erase), + option_ (option), + options_ (&option_info_), + options_count_ (1), + i_ (1), + skip_ (false) + { + option_info_.option = option_.c_str (); + option_info_.search_func = 0; + } + + inline argv_file_scanner:: + argv_file_scanner (const std::string& file, + const std::string& option) + : argv_scanner (0, zero_argc_, 0), + option_ (option), + options_ (&option_info_), + options_count_ (1), + i_ (1), + skip_ (false) + { + option_info_.option = option_.c_str (); + option_info_.search_func = 0; + + load (file); + } + + inline argv_file_scanner:: + argv_file_scanner (int& argc, + char** argv, + const option_info* options, + std::size_t options_count, + bool erase) + : argv_scanner (argc, argv, erase), + options_ (options), + options_count_ (options_count), + i_ (1), + skip_ (false) + { + } + + inline argv_file_scanner:: + argv_file_scanner (int start, + int& argc, + char** argv, + const option_info* options, + std::size_t options_count, + bool erase) + : argv_scanner (start, argc, argv, erase), + options_ (options), + options_count_ (options_count), + i_ (1), + skip_ (false) + { + } + + inline argv_file_scanner:: + argv_file_scanner (const std::string& file, + const option_info* options, + std::size_t options_count) + : argv_scanner (0, zero_argc_, 0), + options_ (options), + options_count_ (options_count), + i_ (1), + skip_ (false) + { + load (file); + } +} + +// options +// + +inline const bool& options:: +build2_metadata () const +{ + return this->build2_metadata_; +} + +inline bool& options:: +build2_metadata () +{ + return this->build2_metadata_; +} + +inline void options:: +build2_metadata(const bool& x) +{ + this->build2_metadata_ = x; +} + +inline const bool& options:: +help () const +{ + return this->help_; +} + +inline bool& options:: +help () +{ + return this->help_; +} + +inline void options:: +help(const bool& x) +{ + this->help_ = x; +} + +inline const bool& options:: +version () const +{ + return this->version_; +} + +inline bool& options:: +version () +{ + return this->version_; +} + +inline void options:: +version(const bool& x) +{ + this->version_ = x; +} + +inline const std::vector<std::string>& options:: +include_path () const +{ + return this->include_path_; +} + +inline std::vector<std::string>& options:: +include_path () +{ + return this->include_path_; +} + +inline void options:: +include_path(const std::vector<std::string>& x) +{ + this->include_path_ = x; +} + +inline bool options:: +include_path_specified () const +{ + return this->include_path_specified_; +} + +inline void options:: +include_path_specified(bool x) +{ + this->include_path_specified_ = x; +} + +inline const std::string& options:: +output_dir () const +{ + return this->output_dir_; +} + +inline std::string& options:: +output_dir () +{ + return this->output_dir_; +} + +inline void options:: +output_dir(const std::string& x) +{ + this->output_dir_ = x; +} + +inline bool options:: +output_dir_specified () const +{ + return this->output_dir_specified_; +} + +inline void options:: +output_dir_specified(bool x) +{ + this->output_dir_specified_ = x; +} + +inline const cxx_version& options:: +std () const +{ + return this->std_; +} + +inline cxx_version& options:: +std () +{ + return this->std_; +} + +inline void options:: +std(const cxx_version& x) +{ + this->std_ = x; +} + +inline bool options:: +std_specified () const +{ + return this->std_specified_; +} + +inline void options:: +std_specified(bool x) +{ + this->std_specified_ = x; +} + +inline const bool& options:: +generate_modifier () const +{ + return this->generate_modifier_; +} + +inline bool& options:: +generate_modifier () +{ + return this->generate_modifier_; +} + +inline void options:: +generate_modifier(const bool& x) +{ + this->generate_modifier_ = x; +} + +inline const bool& options:: +generate_specifier () const +{ + return this->generate_specifier_; +} + +inline bool& options:: +generate_specifier () +{ + return this->generate_specifier_; +} + +inline void options:: +generate_specifier(const bool& x) +{ + this->generate_specifier_ = x; +} + +inline const bool& options:: +generate_parse () const +{ + return this->generate_parse_; +} + +inline bool& options:: +generate_parse () +{ + return this->generate_parse_; +} + +inline void options:: +generate_parse(const bool& x) +{ + this->generate_parse_ = x; +} + +inline const bool& options:: +generate_merge () const +{ + return this->generate_merge_; +} + +inline bool& options:: +generate_merge () +{ + return this->generate_merge_; +} + +inline void options:: +generate_merge(const bool& x) +{ + this->generate_merge_ = x; +} + +inline const bool& options:: +generate_description () const +{ + return this->generate_description_; +} + +inline bool& options:: +generate_description () +{ + return this->generate_description_; +} + +inline void options:: +generate_description(const bool& x) +{ + this->generate_description_ = x; +} + +inline const bool& options:: +generate_file_scanner () const +{ + return this->generate_file_scanner_; +} + +inline bool& options:: +generate_file_scanner () +{ + return this->generate_file_scanner_; +} + +inline void options:: +generate_file_scanner(const bool& x) +{ + this->generate_file_scanner_ = x; +} + +inline const bool& options:: +generate_vector_scanner () const +{ + return this->generate_vector_scanner_; +} + +inline bool& options:: +generate_vector_scanner () +{ + return this->generate_vector_scanner_; +} + +inline void options:: +generate_vector_scanner(const bool& x) +{ + this->generate_vector_scanner_ = x; +} + +inline const bool& options:: +generate_group_scanner () const +{ + return this->generate_group_scanner_; +} + +inline bool& options:: +generate_group_scanner () +{ + return this->generate_group_scanner_; +} + +inline void options:: +generate_group_scanner(const bool& x) +{ + this->generate_group_scanner_ = x; +} + +inline const bool& options:: +suppress_inline () const +{ + return this->suppress_inline_; +} + +inline bool& options:: +suppress_inline () +{ + return this->suppress_inline_; +} + +inline void options:: +suppress_inline(const bool& x) +{ + this->suppress_inline_ = x; +} + +inline const bool& options:: +suppress_cli () const +{ + return this->suppress_cli_; +} + +inline bool& options:: +suppress_cli () +{ + return this->suppress_cli_; +} + +inline void options:: +suppress_cli(const bool& x) +{ + this->suppress_cli_ = x; +} + +inline const std::string& options:: +cli_namespace () const +{ + return this->cli_namespace_; +} + +inline std::string& options:: +cli_namespace () +{ + return this->cli_namespace_; +} + +inline void options:: +cli_namespace(const std::string& x) +{ + this->cli_namespace_ = x; +} + +inline bool options:: +cli_namespace_specified () const +{ + return this->cli_namespace_specified_; +} + +inline void options:: +cli_namespace_specified(bool x) +{ + this->cli_namespace_specified_ = x; +} + +inline const std::string& options:: +ostream_type () const +{ + return this->ostream_type_; +} + +inline std::string& options:: +ostream_type () +{ + return this->ostream_type_; +} + +inline void options:: +ostream_type(const std::string& x) +{ + this->ostream_type_ = x; +} + +inline bool options:: +ostream_type_specified () const +{ + return this->ostream_type_specified_; +} + +inline void options:: +ostream_type_specified(bool x) +{ + this->ostream_type_specified_ = x; +} + +inline const bool& options:: +generate_cxx () const +{ + return this->generate_cxx_; +} + +inline bool& options:: +generate_cxx () +{ + return this->generate_cxx_; +} + +inline void options:: +generate_cxx(const bool& x) +{ + this->generate_cxx_ = x; +} + +inline const bool& options:: +generate_man () const +{ + return this->generate_man_; +} + +inline bool& options:: +generate_man () +{ + return this->generate_man_; +} + +inline void options:: +generate_man(const bool& x) +{ + this->generate_man_ = x; +} + +inline const bool& options:: +generate_html () const +{ + return this->generate_html_; +} + +inline bool& options:: +generate_html () +{ + return this->generate_html_; +} + +inline void options:: +generate_html(const bool& x) +{ + this->generate_html_ = x; +} + +inline const bool& options:: +generate_txt () const +{ + return this->generate_txt_; +} + +inline bool& options:: +generate_txt () +{ + return this->generate_txt_; +} + +inline void options:: +generate_txt(const bool& x) +{ + this->generate_txt_ = x; +} + +inline const bool& options:: +stdout_ () const +{ + return this->stdout__; +} + +inline bool& options:: +stdout_ () +{ + return this->stdout__; +} + +inline void options:: +stdout_(const bool& x) +{ + this->stdout__ = x; +} + +inline const bool& options:: +suppress_undocumented () const +{ + return this->suppress_undocumented_; +} + +inline bool& options:: +suppress_undocumented () +{ + return this->suppress_undocumented_; +} + +inline void options:: +suppress_undocumented(const bool& x) +{ + this->suppress_undocumented_ = x; +} + +inline const bool& options:: +suppress_usage () const +{ + return this->suppress_usage_; +} + +inline bool& options:: +suppress_usage () +{ + return this->suppress_usage_; +} + +inline void options:: +suppress_usage(const bool& x) +{ + this->suppress_usage_ = x; +} + +inline const bool& options:: +long_usage () const +{ + return this->long_usage_; +} + +inline bool& options:: +long_usage () +{ + return this->long_usage_; +} + +inline void options:: +long_usage(const bool& x) +{ + this->long_usage_ = x; +} + +inline const bool& options:: +short_usage () const +{ + return this->short_usage_; +} + +inline bool& options:: +short_usage () +{ + return this->short_usage_; +} + +inline void options:: +short_usage(const bool& x) +{ + this->short_usage_ = x; +} + +inline const std::string& options:: +page_usage () const +{ + return this->page_usage_; +} + +inline std::string& options:: +page_usage () +{ + return this->page_usage_; +} + +inline void options:: +page_usage(const std::string& x) +{ + this->page_usage_ = x; +} + +inline bool options:: +page_usage_specified () const +{ + return this->page_usage_specified_; +} + +inline void options:: +page_usage_specified(bool x) +{ + this->page_usage_specified_ = x; +} + +inline const std::size_t& options:: +option_length () const +{ + return this->option_length_; +} + +inline std::size_t& options:: +option_length () +{ + return this->option_length_; +} + +inline void options:: +option_length(const std::size_t& x) +{ + this->option_length_ = x; +} + +inline bool options:: +option_length_specified () const +{ + return this->option_length_specified_; +} + +inline void options:: +option_length_specified(bool x) +{ + this->option_length_specified_ = x; +} + +inline const bool& options:: +ansi_color () const +{ + return this->ansi_color_; +} + +inline bool& options:: +ansi_color () +{ + return this->ansi_color_; +} + +inline void options:: +ansi_color(const bool& x) +{ + this->ansi_color_ = x; +} + +inline const bool& options:: +exclude_base () const +{ + return this->exclude_base_; +} + +inline bool& options:: +exclude_base () +{ + return this->exclude_base_; +} + +inline void options:: +exclude_base(const bool& x) +{ + this->exclude_base_ = x; +} + +inline const bool& options:: +include_base_last () const +{ + return this->include_base_last_; +} + +inline bool& options:: +include_base_last () +{ + return this->include_base_last_; +} + +inline void options:: +include_base_last(const bool& x) +{ + this->include_base_last_ = x; +} + +inline const std::map<std::string, std::string>& options:: +class_doc () const +{ + return this->class_doc_; +} + +inline std::map<std::string, std::string>& options:: +class_doc () +{ + return this->class_doc_; +} + +inline void options:: +class_doc(const std::map<std::string, std::string>& x) +{ + this->class_doc_ = x; +} + +inline bool options:: +class_doc_specified () const +{ + return this->class_doc_specified_; +} + +inline void options:: +class_doc_specified(bool x) +{ + this->class_doc_specified_ = x; +} + +inline const std::vector<std::string>& options:: +class_ () const +{ + return this->class__; +} + +inline std::vector<std::string>& options:: +class_ () +{ + return this->class__; +} + +inline void options:: +class_(const std::vector<std::string>& x) +{ + this->class__ = x; +} + +inline bool options:: +class__specified () const +{ + return this->class__specified_; +} + +inline void options:: +class__specified(bool x) +{ + this->class__specified_ = x; +} + +inline const std::map<std::string, std::string>& options:: +docvar () const +{ + return this->docvar_; +} + +inline std::map<std::string, std::string>& options:: +docvar () +{ + return this->docvar_; +} + +inline void options:: +docvar(const std::map<std::string, std::string>& x) +{ + this->docvar_ = x; +} + +inline bool options:: +docvar_specified () const +{ + return this->docvar_specified_; +} + +inline void options:: +docvar_specified(bool x) +{ + this->docvar_specified_ = x; +} + +inline const std::vector<std::string>& options:: +link_regex () const +{ + return this->link_regex_; +} + +inline std::vector<std::string>& options:: +link_regex () +{ + return this->link_regex_; +} + +inline void options:: +link_regex(const std::vector<std::string>& x) +{ + this->link_regex_ = x; +} + +inline bool options:: +link_regex_specified () const +{ + return this->link_regex_specified_; +} + +inline void options:: +link_regex_specified(bool x) +{ + this->link_regex_specified_ = x; +} + +inline const bool& options:: +link_regex_trace () const +{ + return this->link_regex_trace_; +} + +inline bool& options:: +link_regex_trace () +{ + return this->link_regex_trace_; +} + +inline void options:: +link_regex_trace(const bool& x) +{ + this->link_regex_trace_ = x; +} + +inline const std::map<char, std::string>& options:: +html_heading_map () const +{ + return this->html_heading_map_; +} + +inline std::map<char, std::string>& options:: +html_heading_map () +{ + return this->html_heading_map_; +} + +inline void options:: +html_heading_map(const std::map<char, std::string>& x) +{ + this->html_heading_map_ = x; +} + +inline bool options:: +html_heading_map_specified () const +{ + return this->html_heading_map_specified_; +} + +inline void options:: +html_heading_map_specified(bool x) +{ + this->html_heading_map_specified_ = x; +} + +inline const bool& options:: +omit_link_check () const +{ + return this->omit_link_check_; +} + +inline bool& options:: +omit_link_check () +{ + return this->omit_link_check_; +} + +inline void options:: +omit_link_check(const bool& x) +{ + this->omit_link_check_ = x; +} + +inline const std::vector<std::string>& options:: +hxx_prologue () const +{ + return this->hxx_prologue_; +} + +inline std::vector<std::string>& options:: +hxx_prologue () +{ + return this->hxx_prologue_; +} + +inline void options:: +hxx_prologue(const std::vector<std::string>& x) +{ + this->hxx_prologue_ = x; +} + +inline bool options:: +hxx_prologue_specified () const +{ + return this->hxx_prologue_specified_; +} + +inline void options:: +hxx_prologue_specified(bool x) +{ + this->hxx_prologue_specified_ = x; +} + +inline const std::vector<std::string>& options:: +ixx_prologue () const +{ + return this->ixx_prologue_; +} + +inline std::vector<std::string>& options:: +ixx_prologue () +{ + return this->ixx_prologue_; +} + +inline void options:: +ixx_prologue(const std::vector<std::string>& x) +{ + this->ixx_prologue_ = x; +} + +inline bool options:: +ixx_prologue_specified () const +{ + return this->ixx_prologue_specified_; +} + +inline void options:: +ixx_prologue_specified(bool x) +{ + this->ixx_prologue_specified_ = x; +} + +inline const std::vector<std::string>& options:: +cxx_prologue () const +{ + return this->cxx_prologue_; +} + +inline std::vector<std::string>& options:: +cxx_prologue () +{ + return this->cxx_prologue_; +} + +inline void options:: +cxx_prologue(const std::vector<std::string>& x) +{ + this->cxx_prologue_ = x; +} + +inline bool options:: +cxx_prologue_specified () const +{ + return this->cxx_prologue_specified_; +} + +inline void options:: +cxx_prologue_specified(bool x) +{ + this->cxx_prologue_specified_ = x; +} + +inline const std::vector<std::string>& options:: +man_prologue () const +{ + return this->man_prologue_; +} + +inline std::vector<std::string>& options:: +man_prologue () +{ + return this->man_prologue_; +} + +inline void options:: +man_prologue(const std::vector<std::string>& x) +{ + this->man_prologue_ = x; +} + +inline bool options:: +man_prologue_specified () const +{ + return this->man_prologue_specified_; +} + +inline void options:: +man_prologue_specified(bool x) +{ + this->man_prologue_specified_ = x; +} + +inline const std::vector<std::string>& options:: +html_prologue () const +{ + return this->html_prologue_; +} + +inline std::vector<std::string>& options:: +html_prologue () +{ + return this->html_prologue_; +} + +inline void options:: +html_prologue(const std::vector<std::string>& x) +{ + this->html_prologue_ = x; +} + +inline bool options:: +html_prologue_specified () const +{ + return this->html_prologue_specified_; +} + +inline void options:: +html_prologue_specified(bool x) +{ + this->html_prologue_specified_ = x; +} + +inline const std::vector<std::string>& options:: +txt_prologue () const +{ + return this->txt_prologue_; +} + +inline std::vector<std::string>& options:: +txt_prologue () +{ + return this->txt_prologue_; +} + +inline void options:: +txt_prologue(const std::vector<std::string>& x) +{ + this->txt_prologue_ = x; +} + +inline bool options:: +txt_prologue_specified () const +{ + return this->txt_prologue_specified_; +} + +inline void options:: +txt_prologue_specified(bool x) +{ + this->txt_prologue_specified_ = x; +} + +inline const std::vector<std::string>& options:: +hxx_epilogue () const +{ + return this->hxx_epilogue_; +} + +inline std::vector<std::string>& options:: +hxx_epilogue () +{ + return this->hxx_epilogue_; +} + +inline void options:: +hxx_epilogue(const std::vector<std::string>& x) +{ + this->hxx_epilogue_ = x; +} + +inline bool options:: +hxx_epilogue_specified () const +{ + return this->hxx_epilogue_specified_; +} + +inline void options:: +hxx_epilogue_specified(bool x) +{ + this->hxx_epilogue_specified_ = x; +} + +inline const std::vector<std::string>& options:: +ixx_epilogue () const +{ + return this->ixx_epilogue_; +} + +inline std::vector<std::string>& options:: +ixx_epilogue () +{ + return this->ixx_epilogue_; +} + +inline void options:: +ixx_epilogue(const std::vector<std::string>& x) +{ + this->ixx_epilogue_ = x; +} + +inline bool options:: +ixx_epilogue_specified () const +{ + return this->ixx_epilogue_specified_; +} + +inline void options:: +ixx_epilogue_specified(bool x) +{ + this->ixx_epilogue_specified_ = x; +} + +inline const std::vector<std::string>& options:: +cxx_epilogue () const +{ + return this->cxx_epilogue_; +} + +inline std::vector<std::string>& options:: +cxx_epilogue () +{ + return this->cxx_epilogue_; +} + +inline void options:: +cxx_epilogue(const std::vector<std::string>& x) +{ + this->cxx_epilogue_ = x; +} + +inline bool options:: +cxx_epilogue_specified () const +{ + return this->cxx_epilogue_specified_; +} + +inline void options:: +cxx_epilogue_specified(bool x) +{ + this->cxx_epilogue_specified_ = x; +} + +inline const std::vector<std::string>& options:: +man_epilogue () const +{ + return this->man_epilogue_; +} + +inline std::vector<std::string>& options:: +man_epilogue () +{ + return this->man_epilogue_; +} + +inline void options:: +man_epilogue(const std::vector<std::string>& x) +{ + this->man_epilogue_ = x; +} + +inline bool options:: +man_epilogue_specified () const +{ + return this->man_epilogue_specified_; +} + +inline void options:: +man_epilogue_specified(bool x) +{ + this->man_epilogue_specified_ = x; +} + +inline const std::vector<std::string>& options:: +html_epilogue () const +{ + return this->html_epilogue_; +} + +inline std::vector<std::string>& options:: +html_epilogue () +{ + return this->html_epilogue_; +} + +inline void options:: +html_epilogue(const std::vector<std::string>& x) +{ + this->html_epilogue_ = x; +} + +inline bool options:: +html_epilogue_specified () const +{ + return this->html_epilogue_specified_; +} + +inline void options:: +html_epilogue_specified(bool x) +{ + this->html_epilogue_specified_ = x; +} + +inline const std::vector<std::string>& options:: +txt_epilogue () const +{ + return this->txt_epilogue_; +} + +inline std::vector<std::string>& options:: +txt_epilogue () +{ + return this->txt_epilogue_; +} + +inline void options:: +txt_epilogue(const std::vector<std::string>& x) +{ + this->txt_epilogue_ = x; +} + +inline bool options:: +txt_epilogue_specified () const +{ + return this->txt_epilogue_specified_; +} + +inline void options:: +txt_epilogue_specified(bool x) +{ + this->txt_epilogue_specified_ = x; +} + +inline const std::string& options:: +hxx_prologue_file () const +{ + return this->hxx_prologue_file_; +} + +inline std::string& options:: +hxx_prologue_file () +{ + return this->hxx_prologue_file_; +} + +inline void options:: +hxx_prologue_file(const std::string& x) +{ + this->hxx_prologue_file_ = x; +} + +inline bool options:: +hxx_prologue_file_specified () const +{ + return this->hxx_prologue_file_specified_; +} + +inline void options:: +hxx_prologue_file_specified(bool x) +{ + this->hxx_prologue_file_specified_ = x; +} + +inline const std::string& options:: +ixx_prologue_file () const +{ + return this->ixx_prologue_file_; +} + +inline std::string& options:: +ixx_prologue_file () +{ + return this->ixx_prologue_file_; +} + +inline void options:: +ixx_prologue_file(const std::string& x) +{ + this->ixx_prologue_file_ = x; +} + +inline bool options:: +ixx_prologue_file_specified () const +{ + return this->ixx_prologue_file_specified_; +} + +inline void options:: +ixx_prologue_file_specified(bool x) +{ + this->ixx_prologue_file_specified_ = x; +} + +inline const std::string& options:: +cxx_prologue_file () const +{ + return this->cxx_prologue_file_; +} + +inline std::string& options:: +cxx_prologue_file () +{ + return this->cxx_prologue_file_; +} + +inline void options:: +cxx_prologue_file(const std::string& x) +{ + this->cxx_prologue_file_ = x; +} + +inline bool options:: +cxx_prologue_file_specified () const +{ + return this->cxx_prologue_file_specified_; +} + +inline void options:: +cxx_prologue_file_specified(bool x) +{ + this->cxx_prologue_file_specified_ = x; +} + +inline const std::string& options:: +man_prologue_file () const +{ + return this->man_prologue_file_; +} + +inline std::string& options:: +man_prologue_file () +{ + return this->man_prologue_file_; +} + +inline void options:: +man_prologue_file(const std::string& x) +{ + this->man_prologue_file_ = x; +} + +inline bool options:: +man_prologue_file_specified () const +{ + return this->man_prologue_file_specified_; +} + +inline void options:: +man_prologue_file_specified(bool x) +{ + this->man_prologue_file_specified_ = x; +} + +inline const std::string& options:: +html_prologue_file () const +{ + return this->html_prologue_file_; +} + +inline std::string& options:: +html_prologue_file () +{ + return this->html_prologue_file_; +} + +inline void options:: +html_prologue_file(const std::string& x) +{ + this->html_prologue_file_ = x; +} + +inline bool options:: +html_prologue_file_specified () const +{ + return this->html_prologue_file_specified_; +} + +inline void options:: +html_prologue_file_specified(bool x) +{ + this->html_prologue_file_specified_ = x; +} + +inline const std::string& options:: +txt_prologue_file () const +{ + return this->txt_prologue_file_; +} + +inline std::string& options:: +txt_prologue_file () +{ + return this->txt_prologue_file_; +} + +inline void options:: +txt_prologue_file(const std::string& x) +{ + this->txt_prologue_file_ = x; +} + +inline bool options:: +txt_prologue_file_specified () const +{ + return this->txt_prologue_file_specified_; +} + +inline void options:: +txt_prologue_file_specified(bool x) +{ + this->txt_prologue_file_specified_ = x; +} + +inline const std::string& options:: +hxx_epilogue_file () const +{ + return this->hxx_epilogue_file_; +} + +inline std::string& options:: +hxx_epilogue_file () +{ + return this->hxx_epilogue_file_; +} + +inline void options:: +hxx_epilogue_file(const std::string& x) +{ + this->hxx_epilogue_file_ = x; +} + +inline bool options:: +hxx_epilogue_file_specified () const +{ + return this->hxx_epilogue_file_specified_; +} + +inline void options:: +hxx_epilogue_file_specified(bool x) +{ + this->hxx_epilogue_file_specified_ = x; +} + +inline const std::string& options:: +ixx_epilogue_file () const +{ + return this->ixx_epilogue_file_; +} + +inline std::string& options:: +ixx_epilogue_file () +{ + return this->ixx_epilogue_file_; +} + +inline void options:: +ixx_epilogue_file(const std::string& x) +{ + this->ixx_epilogue_file_ = x; +} + +inline bool options:: +ixx_epilogue_file_specified () const +{ + return this->ixx_epilogue_file_specified_; +} + +inline void options:: +ixx_epilogue_file_specified(bool x) +{ + this->ixx_epilogue_file_specified_ = x; +} + +inline const std::string& options:: +cxx_epilogue_file () const +{ + return this->cxx_epilogue_file_; +} + +inline std::string& options:: +cxx_epilogue_file () +{ + return this->cxx_epilogue_file_; +} + +inline void options:: +cxx_epilogue_file(const std::string& x) +{ + this->cxx_epilogue_file_ = x; +} + +inline bool options:: +cxx_epilogue_file_specified () const +{ + return this->cxx_epilogue_file_specified_; +} + +inline void options:: +cxx_epilogue_file_specified(bool x) +{ + this->cxx_epilogue_file_specified_ = x; +} + +inline const std::string& options:: +man_epilogue_file () const +{ + return this->man_epilogue_file_; +} + +inline std::string& options:: +man_epilogue_file () +{ + return this->man_epilogue_file_; +} + +inline void options:: +man_epilogue_file(const std::string& x) +{ + this->man_epilogue_file_ = x; +} + +inline bool options:: +man_epilogue_file_specified () const +{ + return this->man_epilogue_file_specified_; +} + +inline void options:: +man_epilogue_file_specified(bool x) +{ + this->man_epilogue_file_specified_ = x; +} + +inline const std::string& options:: +html_epilogue_file () const +{ + return this->html_epilogue_file_; +} + +inline std::string& options:: +html_epilogue_file () +{ + return this->html_epilogue_file_; +} + +inline void options:: +html_epilogue_file(const std::string& x) +{ + this->html_epilogue_file_ = x; +} + +inline bool options:: +html_epilogue_file_specified () const +{ + return this->html_epilogue_file_specified_; +} + +inline void options:: +html_epilogue_file_specified(bool x) +{ + this->html_epilogue_file_specified_ = x; +} + +inline const std::string& options:: +txt_epilogue_file () const +{ + return this->txt_epilogue_file_; +} + +inline std::string& options:: +txt_epilogue_file () +{ + return this->txt_epilogue_file_; +} + +inline void options:: +txt_epilogue_file(const std::string& x) +{ + this->txt_epilogue_file_ = x; +} + +inline bool options:: +txt_epilogue_file_specified () const +{ + return this->txt_epilogue_file_specified_; +} + +inline void options:: +txt_epilogue_file_specified(bool x) +{ + this->txt_epilogue_file_specified_ = x; +} + +inline const std::string& options:: +output_prefix () const +{ + return this->output_prefix_; +} + +inline std::string& options:: +output_prefix () +{ + return this->output_prefix_; +} + +inline void options:: +output_prefix(const std::string& x) +{ + this->output_prefix_ = x; +} + +inline bool options:: +output_prefix_specified () const +{ + return this->output_prefix_specified_; +} + +inline void options:: +output_prefix_specified(bool x) +{ + this->output_prefix_specified_ = x; +} + +inline const std::string& options:: +output_suffix () const +{ + return this->output_suffix_; +} + +inline std::string& options:: +output_suffix () +{ + return this->output_suffix_; +} + +inline void options:: +output_suffix(const std::string& x) +{ + this->output_suffix_ = x; +} + +inline bool options:: +output_suffix_specified () const +{ + return this->output_suffix_specified_; +} + +inline void options:: +output_suffix_specified(bool x) +{ + this->output_suffix_specified_ = x; +} + +inline const std::string& options:: +hxx_suffix () const +{ + return this->hxx_suffix_; +} + +inline std::string& options:: +hxx_suffix () +{ + return this->hxx_suffix_; +} + +inline void options:: +hxx_suffix(const std::string& x) +{ + this->hxx_suffix_ = x; +} + +inline bool options:: +hxx_suffix_specified () const +{ + return this->hxx_suffix_specified_; +} + +inline void options:: +hxx_suffix_specified(bool x) +{ + this->hxx_suffix_specified_ = x; +} + +inline const std::string& options:: +ixx_suffix () const +{ + return this->ixx_suffix_; +} + +inline std::string& options:: +ixx_suffix () +{ + return this->ixx_suffix_; +} + +inline void options:: +ixx_suffix(const std::string& x) +{ + this->ixx_suffix_ = x; +} + +inline bool options:: +ixx_suffix_specified () const +{ + return this->ixx_suffix_specified_; +} + +inline void options:: +ixx_suffix_specified(bool x) +{ + this->ixx_suffix_specified_ = x; +} + +inline const std::string& options:: +cxx_suffix () const +{ + return this->cxx_suffix_; +} + +inline std::string& options:: +cxx_suffix () +{ + return this->cxx_suffix_; +} + +inline void options:: +cxx_suffix(const std::string& x) +{ + this->cxx_suffix_ = x; +} + +inline bool options:: +cxx_suffix_specified () const +{ + return this->cxx_suffix_specified_; +} + +inline void options:: +cxx_suffix_specified(bool x) +{ + this->cxx_suffix_specified_ = x; +} + +inline const std::string& options:: +man_suffix () const +{ + return this->man_suffix_; +} + +inline std::string& options:: +man_suffix () +{ + return this->man_suffix_; +} + +inline void options:: +man_suffix(const std::string& x) +{ + this->man_suffix_ = x; +} + +inline bool options:: +man_suffix_specified () const +{ + return this->man_suffix_specified_; +} + +inline void options:: +man_suffix_specified(bool x) +{ + this->man_suffix_specified_ = x; +} + +inline const std::string& options:: +html_suffix () const +{ + return this->html_suffix_; +} + +inline std::string& options:: +html_suffix () +{ + return this->html_suffix_; +} + +inline void options:: +html_suffix(const std::string& x) +{ + this->html_suffix_ = x; +} + +inline bool options:: +html_suffix_specified () const +{ + return this->html_suffix_specified_; +} + +inline void options:: +html_suffix_specified(bool x) +{ + this->html_suffix_specified_ = x; +} + +inline const std::string& options:: +txt_suffix () const +{ + return this->txt_suffix_; +} + +inline std::string& options:: +txt_suffix () +{ + return this->txt_suffix_; +} + +inline void options:: +txt_suffix(const std::string& x) +{ + this->txt_suffix_ = x; +} + +inline bool options:: +txt_suffix_specified () const +{ + return this->txt_suffix_specified_; +} + +inline void options:: +txt_suffix_specified(bool x) +{ + this->txt_suffix_specified_ = x; +} + +inline const std::string& options:: +option_prefix () const +{ + return this->option_prefix_; +} + +inline std::string& options:: +option_prefix () +{ + return this->option_prefix_; +} + +inline void options:: +option_prefix(const std::string& x) +{ + this->option_prefix_ = x; +} + +inline bool options:: +option_prefix_specified () const +{ + return this->option_prefix_specified_; +} + +inline void options:: +option_prefix_specified(bool x) +{ + this->option_prefix_specified_ = x; +} + +inline const std::string& options:: +option_separator () const +{ + return this->option_separator_; +} + +inline std::string& options:: +option_separator () +{ + return this->option_separator_; +} + +inline void options:: +option_separator(const std::string& x) +{ + this->option_separator_ = x; +} + +inline bool options:: +option_separator_specified () const +{ + return this->option_separator_specified_; +} + +inline void options:: +option_separator_specified(bool x) +{ + this->option_separator_specified_ = x; +} + +inline const bool& options:: +keep_separator () const +{ + return this->keep_separator_; +} + +inline bool& options:: +keep_separator () +{ + return this->keep_separator_; +} + +inline void options:: +keep_separator(const bool& x) +{ + this->keep_separator_ = x; +} + +inline const bool& options:: +no_combined_flags () const +{ + return this->no_combined_flags_; +} + +inline bool& options:: +no_combined_flags () +{ + return this->no_combined_flags_; +} + +inline void options:: +no_combined_flags(const bool& x) +{ + this->no_combined_flags_ = x; +} + +inline const bool& options:: +no_combined_values () const +{ + return this->no_combined_values_; +} + +inline bool& options:: +no_combined_values () +{ + return this->no_combined_values_; +} + +inline void options:: +no_combined_values(const bool& x) +{ + this->no_combined_values_ = x; +} + +inline const bool& options:: +include_with_brackets () const +{ + return this->include_with_brackets_; +} + +inline bool& options:: +include_with_brackets () +{ + return this->include_with_brackets_; +} + +inline void options:: +include_with_brackets(const bool& x) +{ + this->include_with_brackets_ = x; +} + +inline const std::string& options:: +include_prefix () const +{ + return this->include_prefix_; +} + +inline std::string& options:: +include_prefix () +{ + return this->include_prefix_; +} + +inline void options:: +include_prefix(const std::string& x) +{ + this->include_prefix_ = x; +} + +inline bool options:: +include_prefix_specified () const +{ + return this->include_prefix_specified_; +} + +inline void options:: +include_prefix_specified(bool x) +{ + this->include_prefix_specified_ = x; +} + +inline const std::string& options:: +guard_prefix () const +{ + return this->guard_prefix_; +} + +inline std::string& options:: +guard_prefix () +{ + return this->guard_prefix_; +} + +inline void options:: +guard_prefix(const std::string& x) +{ + this->guard_prefix_ = x; +} + +inline bool options:: +guard_prefix_specified () const +{ + return this->guard_prefix_specified_; +} + +inline void options:: +guard_prefix_specified(bool x) +{ + this->guard_prefix_specified_ = x; +} + +inline const std::map<std::string, std::string>& options:: +reserved_name () const +{ + return this->reserved_name_; +} + +inline std::map<std::string, std::string>& options:: +reserved_name () +{ + return this->reserved_name_; +} + +inline void options:: +reserved_name(const std::map<std::string, std::string>& x) +{ + this->reserved_name_ = x; +} + +inline bool options:: +reserved_name_specified () const +{ + return this->reserved_name_specified_; +} + +inline void options:: +reserved_name_specified(bool x) +{ + this->reserved_name_specified_ = x; +} + +inline const std::string& options:: +options_file () const +{ + return this->options_file_; +} + +inline std::string& options:: +options_file () +{ + return this->options_file_; +} + +inline void options:: +options_file(const std::string& x) +{ + this->options_file_ = x; +} + +inline bool options:: +options_file_specified () const +{ + return this->options_file_specified_; +} + +inline void options:: +options_file_specified(bool x) +{ + this->options_file_specified_ = x; +} + +// Begin epilogue. +// +// +// End epilogue. diff --git a/cli/cli/parser.cxx b/cli/cli/parser.cxx new file mode 100644 index 0000000..4685edc --- /dev/null +++ b/cli/cli/parser.cxx @@ -0,0 +1,1728 @@ +// file : cli/parser.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef _WIN32 +# include <unistd.h> // stat +# include <sys/types.h> // stat +# include <sys/stat.h> // stat +#else +# include <sys/types.h> // _stat +# include <sys/stat.h> // _stat(), S_I* + +# ifdef _MSC_VER // Unlikely to be fixed in newer versions. +# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +# endif +#endif + +#include <fstream> +#include <sstream> +#include <iostream> + +#include <cli/token.hxx> +#include <cli/lexer.hxx> +#include <cli/parser.hxx> + +#include <cli/semantics.hxx> + +using namespace std; +using namespace semantics; + +// Check that the file exist without checking for permissions, etc. +// +inline static bool +file_exists (const path& p) +{ +#ifndef _WIN32 + struct stat s; + int r (stat (p.string ().c_str (), &s)); +#else + struct _stat s; + int r (_stat (p.string ().c_str (), &s)); +#endif + + return r == 0 && S_ISREG (s.st_mode); +} + +const char* keywords[] = +{ + "include", + "namespace", + "class", + "signed", + "unsigned", + "bool", + "char", + "wchar_t", + "short", + "int", + "long", + "float", + "double" +}; + +const char* punctuation[] = { + ";", ",", ":", "::", "{", "}", /*"(", ")",*/ "=", "|"}; + +// Output the token type and value in a format suitable for diagnostics. +// +std::ostream& +operator<< (std::ostream& os, token const& t) +{ + switch (t.type ()) + { + case token::t_eos: + { + os << "end-of-stream"; + break; + } + case token::t_keyword: + { + os << "keyword '" << keywords[t.keyword ()] << "'"; + break; + } + case token::t_identifier: + { + os << "identifier '" << t.identifier () << "'"; + break; + } + case token::t_punctuation: + { + os << "'" << punctuation[t.punctuation ()] << "'"; + break; + } + case token::t_cxx_path_lit: + { + os << "c++ path literal"; + break; + } + case token::t_cli_path_lit: + { + os << "cli path literal"; + break; + } + case token::t_string_lit: + { + os << "string literal"; + break; + } + case token::t_char_lit: + { + os << "char literal"; + break; + } + case token::t_bool_lit: + { + os << "bool literal"; + break; + } + case token::t_int_lit: + { + os << "integer literal"; + break; + } + case token::t_float_lit: + { + os << "floating point literal"; + break; + } + case token::t_call_expr: + { + os << "call expression"; + break; + } + case token::t_template_expr: + { + os << "template expression"; + break; + } + } + + return os; +} + +// RAII-style set new value on construction, restore old one on destruction. +// +template <typename T> +struct auto_restore +{ + auto_restore (T*& var, T* new_val = 0) + : var_ (var), old_val_ (var_) + { + if (new_val != 0) + var_ = new_val; + } + + void + set (T* new_val) {var_ = new_val;} + + ~auto_restore () {var_ = old_val_;} + +private: + T*& var_; + T* old_val_; +}; + + +void parser:: +recover (token& t) +{ + // Recover by skipping past next ';' or '}'. + // + for (;; t = lexer_->next ()) + { + if (t.type () == token::t_eos) + break; + + token::punctuation_type p (t.punctuation ()); + + if (p == token::p_semi || p == token::p_rcbrace) + { + t = lexer_->next (); + break; + } + } +} + +unique_ptr<cli_unit> parser:: +parse (std::istream& is, path const& p) +{ + unique_ptr<cli_unit> unit (new cli_unit (p, 1, 1)); + + { + path ap (p); + ap.absolute (); + ap.normalize (); + include_map_[ap] = unit.get (); + } + + root_ = cur_ = unit.get (); + + lexer l (is, p.string ()); + lexer_ = &l; + + doc_count_ = 0; + + path_ = &p; + valid_ = true; + + def_unit (); + + if (!valid_ || !l.valid ()) + throw invalid_input (); + + return unit; +} + +void parser:: +def_unit () +{ + token t (lexer_->next ()); + + // include-decl-seq + // + for (token::keyword_type k (t.keyword ()); + k == token::k_include || k == token::k_source; + k = t.keyword ()) + { + try + { + if (k == token::k_include) + include_decl (); + else + source_decl (); + + t = lexer_->next (); + } + catch (error const&) + { + valid_ = false; + recover (t); + } + } + + auto_restore<scope> new_scope (scope_, cur_); + + // decl-seq + // + while (t.type () != token::t_eos) + { + try + { + if (t.keyword () == token::k_source) + { + try + { + source_decl (); + t = lexer_->next (); + } + catch (error const&) + { + valid_ = false; + recover (t); + } + + continue; + } + + if (decl (t)) + { + t = lexer_->next (); + continue; + } + + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "expected namespace, class, or documentation instead of " + << t << endl; + throw error (); + } + catch (error const&) + { + valid_ = false; + break; // Non-recoverable error. + } + } +} + +void parser:: +source_decl () +{ + token t (lexer_->next ()); + + if (t.type () != token::t_cli_path_lit) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "expected cli path literal instead of " << t << endl; + throw error (); + } + + string const& l (t.literal ()); + bool q (l[0] == '"'); // Quote or braket include? + + path f; + try + { + f = path (string (l, 1, l.size () - 2)); + } + catch (const invalid_path& e) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "'" << e.path () << "' is not a valid filesystem path" << endl; + valid_ = false; + } + + if (valid_) + { + path p; + + // If this is a quote include, then include relative to the current + // file. + // + if (q) + { + p = path_->directory () / f; + p.normalize (); + } + // Otherwise search the include directories (-I). + // + else + { + for (paths::const_iterator i (include_paths_.begin ()); + i != include_paths_.end (); ++i) + { + p = *i / f; + p.normalize (); + + if (file_exists (p)) + break; + + p.clear (); + } + + if (p.empty ()) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": " + << "error: file '" << f << "' not found in any of the " + << "include search directories (-I)" << endl; + valid_ = false; + } + } + + if (valid_) + { + auto_restore<path const> new_path (path_, &p); + + ifstream ifs (p.string ().c_str ()); + if (ifs.is_open ()) + { + ifs.exceptions (ifstream::failbit | ifstream::badbit); + + try + { + lexer l (ifs, p.string ()); + auto_restore<lexer> new_lexer (lexer_, &l); + + def_unit (); + + if (!l.valid ()) + valid_ = false; + } + catch (std::ios_base::failure const&) + { + cerr << p << ": error: read failure" << endl; + valid_ = false; + } + } + else + { + cerr << p << ": error: unable to open in read mode" << endl; + valid_ = false; + } + } + } + + t = lexer_->next (); + + if (t.punctuation () != token::p_semi) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "expected ';' instead of " << t << endl; + throw error (); + } +} + +void parser:: +include_decl () +{ + token t (lexer_->next ()); + token::token_type tt (t.type ()); + + if (tt != token::t_cxx_path_lit && tt != token::t_cli_path_lit) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "expected path literal instead of " << t << endl; + throw error (); + } + + string const& l (t.literal ()); + includes::kind_type ik (l[0] == '<' ? includes::bracket : includes::quote); + + path f; + try + { + f = path (string (l, 1, l.size () - 2)); + } + catch (const invalid_path& e) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "'" << e.path () << "' is not a valid filesystem path" << endl; + valid_ = false; + } + + if (valid_) + { + if (tt == token::t_cxx_path_lit) + { + cxx_unit& n ( + root_->new_node<cxx_unit> (*path_, t.line (), t.column ())); + root_->new_edge<cxx_includes> (*cur_, n, ik, f); + } + else + { + path p; + // If this is a quote include, then include relative to the current + // file. + // + if (ik == includes::quote) + { + p = path_->directory () / f; + p.normalize (); + } + // Otherwise search the include directories (-I). + // + else + { + for (paths::const_iterator i (include_paths_.begin ()); + i != include_paths_.end (); ++i) + { + p = *i / f; + p.normalize (); + + if (file_exists (p)) + break; + + p.clear (); + } + + if (p.empty ()) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": " + << "error: file '" << f << "' not found in any of the " + << "include search directories (-I)" << endl; + valid_ = false; + } + } + + if (valid_) + { + // Detect and ignore multiple inclusions. + // + path ap (p); + ap.absolute (); + ap.normalize (); + + include_map::iterator it (include_map_.find (ap)); + if (it == include_map_.end ()) + { + cli_unit& n (root_->new_node<cli_unit> (p, 1, 1)); + root_->new_edge<cli_includes> (*cur_, n, ik, f); + include_map_[ap] = &n; + + auto_restore<cli_unit> new_cur (cur_, &n); + auto_restore<path const> new_path (path_, &p); + + ifstream ifs (p.string ().c_str ()); + if (ifs.is_open ()) + { + ifs.exceptions (ifstream::failbit | ifstream::badbit); + + try + { + lexer l (ifs, p.string ()); + auto_restore<lexer> new_lexer (lexer_, &l); + + def_unit (); + + if (!l.valid ()) + valid_ = false; + } + catch (std::ios_base::failure const&) + { + cerr << p << ": error: read failure" << endl; + valid_ = false; + } + } + else + { + cerr << p << ": error: unable to open in read mode" << endl; + valid_ = false; + } + } + else + root_->new_edge<cli_includes> (*cur_, *it->second, ik, f); + } + } + } + + t = lexer_->next (); + + if (t.punctuation () != token::p_semi) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "expected ';' instead of " << t << endl; + throw error (); + } +} + +bool parser:: +decl (token& t) +{ + switch (t.type ()) + { + case token::t_keyword: + { + switch (t.keyword ()) + { + case token::k_namespace: + { + namespace_def (); + return true; + } + case token::k_class: + { + class_def (); + return true; + } + default: + break; + } + + break; + } + case token::t_punctuation: + { + if (t.punctuation () != token::p_lcbrace) + break; + } + // Fall through. + case token::t_string_lit: + { + scope_doc (t); + return true; + } + default: + break; + } + + return false; +} + +void parser:: +scope_doc (token& t) +{ + size_t ln (t.line ()), cl (t.column ()); + + // Use a counter to give scope-level docs unique names. We use a + // single counter throughout all units/scope because we could be + // reopening namespaces. + // + if (t.type () == token::t_string_lit) + { + // string-literal + // + if (valid_) + { + // Enter each ""-enclosed string as a separate documentation + // entry, handle documentation variables. + // + const string& l (t.literal ()); + + char p ('\0'); + for (size_t b (0), e (1); e < l.size (); ++e) + { + if (l[e] == '"' && p != '\\') + { + string s (doc_string (l.c_str () + b, e - b + 1)); + + if (!s.empty ()) + { + doc& d (root_->new_node<doc> (*path_, ln, cl)); + + // See if this is a variable assignment: "\<var>=<val>". + // + size_t p (0); // '=' position. + if (s.size () >= 3 && s[0] == '\\' && s[1] != '\\') + { + for (p = 1; p != s.size (); ++p) + { + char c (s[p]); + + // Variable name should be a C identifier. + // + if (!(c == '_' || + ('a' <= c && c <= 'z') || + ('A' <= c && c <= 'Z') || + (p != 1 && '0' <= c && c <= '9'))) + break; + } + + if (p == s.size () || s[p] != '=' || p == 1) // Not a variable. + p = 0; + } + + if (p != 0) + { + root_->new_edge<names> ( + *scope_, d, "var: " + string (s, 1, p - 1)); + s = string (s, p + 1); + } + else + { + ostringstream os; + os << "doc: " << doc_count_++; + root_->new_edge<names> (*scope_, d, os.str ()); + } + + d.push_back (s); // move(). + } + + // If we have more, then make b point to the opening '"'. Second + // ++e in for() above will make e point to the character after it. + // + b = ++e; + continue; + } + + // We need to keep track of \\ escapings so we don't confuse + // them with \", as in \\". + // + if (l[e] == '\\' && p == '\\') + p = '\0'; + else + p = l[e]; + } + } + } + else + { + // doc-string-seq + // + assert (t.punctuation () == token::p_lcbrace); + + doc* d (0); + if (valid_) + { + ostringstream os; + os << "doc: " << doc_count_++; + + d = &root_->new_node<doc> (*path_, ln, cl); + root_->new_edge<names> (*scope_, *d, os.str ()); + } + + for (t = lexer_->next ();; t = lexer_->next ()) + { + if (t.type () != token::t_string_lit) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "expected documentation string instead of " << t << endl; + throw error (); + } + + if (valid_) + d->push_back (doc_string (t.literal ().c_str (), + t.literal ().size ())); + + t = lexer_->next (); + + if (t.punctuation () != token::p_comma) + break; + } + + if (t.punctuation () != token::p_rcbrace) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "expected '}' instead of " << t << endl; + throw error (); + } + } +} + +void parser:: +namespace_def () +{ + token t (lexer_->next ()); + + if (t.type () != token::t_identifier) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "expected identifier instead of " << t << endl; + throw error (); + } + + auto_restore<scope> new_scope (scope_); + + if (valid_) + { + namespace_& n ( + root_->new_node<namespace_> (*path_, t.line (), t.column ())); + root_->new_edge<names> (*scope_, n, t.identifier ()); + new_scope.set (&n); + } + + t = lexer_->next (); + + if (t.punctuation () != token::p_lcbrace) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "expected '{' instead of " << t << endl; + throw error (); + } + + // decl-seq + // + t = lexer_->next (); + + while (decl (t)) + t = lexer_->next (); + + if (t.punctuation () != token::p_rcbrace) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "expected namespace, class, documentation, or '}' instead of " + << t << endl; + throw error (); + } +} + +void parser:: +class_def () +{ + token t (lexer_->next ()); + + if (t.type () != token::t_identifier) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "expected identifier instead of " << t << endl; + throw error (); + } + + class_* n (0); + if (valid_) + { + n = &root_->new_node<class_> (*path_, t.line (), t.column ()); + root_->new_edge<names> (*scope_, *n, t.identifier ()); + } + + t = lexer_->next (); + + // inheritance-spec + // + if (t.punctuation () == token::p_colon) + { + for (;;) + { + t = lexer_->next (); + size_t line (t.line ()), col (t.column ()); + + string name; + if (!qualified_name (t, name)) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "expected qualified name instead of " << t << endl; + throw error (); + } + + string ns; + + // If it is a fully-qualifed name, then start from the global namespace. + // Otherwise, from the current scope. + // + if (name[0] == ':') + name = string (name, 2, string::npos); + else + ns = scope_->fq_name (); + + if (class_* b = cur_->lookup<class_> (ns, name)) + root_->new_edge<inherits> (*n, *b); + else + { + cerr << *path_ << ':' << line << ':' << col << ": error: " + << "unable to resolve base class '" << name << "'" << endl; + valid_ = false; + } + + if (t.punctuation () != token::p_comma) + break; + } + } + + // abstract-spec + // + if (t.punctuation () == token::p_eq) + { + t = lexer_->next (); + + if (t.type () != token::t_int_lit || t.literal () != "0") + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "expected '0' instead of " << t << endl; + throw error (); + } + + if (n != 0) + n->abstract (true); + + t = lexer_->next (); + } + + if (t.punctuation () != token::p_lcbrace) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "expected '{' instead of " << t << endl; + throw error (); + } + + auto_restore<scope> new_scope (scope_, n); + + // class-decl-seq + // + t = lexer_->next (); + + for (;;) + { + try + { + if (t.type () == token::t_string_lit || + t.punctuation () == token::p_lcbrace) + { + scope_doc (t); + t = lexer_->next (); + } + else + { + if (!option_def (t)) + break; + } + } + catch (error const&) + { + valid_ = false; + recover (t); + } + } + + if (t.punctuation () != token::p_rcbrace) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "expected option, documentation, or '}' instead of " << t << endl; + throw error (); + } + + t = lexer_->next (); + + if (t.punctuation () != token::p_semi) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "expected ';' instead of " << t << endl; + throw error (); + } +} + +bool parser:: +option_def (token& t) +{ + size_t l (t.line ()), c (t.column ()); + + // type-spec + // + // These two functions set t to the next token if they return + // true. + // + string type_name; + + if (!qualified_name (t, type_name) && !fundamental_type (t, type_name)) + return false; + + option* o (0); + + if (valid_) + { + o = &root_->new_node<option> (*path_, l, c); + type& t (root_->new_type (*path_, l, c, type_name)); + root_->new_edge<belongs> (*o, t); + } + + // option-name-seq + // + names::name_list nl; + for (;;) + { + switch (t.type ()) + { + case token::t_identifier: + { + if (valid_) + nl.push_back (t.identifier ()); + + break; + } + case token::t_string_lit: + { + if (valid_) + { + // Get rid of '"'. + // + string r; + string const& l (t.literal ()); + char p ('\0'); + + for (size_t i (0), n (l.size ()); i < n; ++i) + { + if (l[i] == '"' && p != '\\') + continue; + + // We need to keep track of \\ escapings so we don't confuse + // them with \", as in "\\". + // + if (l[i] == '\\' && p == '\\') + p = '\0'; + else + p = l[i]; + + r += l[i]; + } + + nl.push_back (r); + } + + break; + } + default: + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "option name expected instead of " << t << endl; + throw error (); + } + } + + t = lexer_->next (); + + if (t.punctuation () == token::p_or) + t = lexer_->next (); + else + break; + } + + if (valid_) + root_->new_edge<names> (*scope_, *o, nl); + + // initializer + // + std::string ev; + expression::expression_type et; + + if (t.punctuation () == token::p_eq) + { + // assignment initiaizer + // + t = lexer_->next (); + + l = t.line (); + c = t.column (); + + if (qualified_name (t, ev)) + { + et = expression::identifier; + } + else + { + switch (t.type ()) + { + case token::t_string_lit: + { + ev = t.literal (); + et = expression::string_lit; + t = lexer_->next (); + break; + } + case token::t_char_lit: + { + ev = t.literal (); + et = expression::char_lit; + t = lexer_->next (); + break; + } + case token::t_bool_lit: + { + ev = t.literal (); + et = expression::bool_lit; + t = lexer_->next (); + break; + } + case token::t_int_lit: + { + ev = t.literal (); + et = expression::int_lit; + t = lexer_->next (); + break; + } + case token::t_float_lit: + { + ev = t.literal (); + et = expression::float_lit; + t = lexer_->next (); + break; + } + case token::t_call_expr: + { + ev = t.expression (); + et = expression::call_expr; + t = lexer_->next (); + break; + } + default: + { + cerr << *path_ << ':' << t.line () << ':' << t.column () + << ": error: expected intializer instead of " << t << endl; + throw error (); + } + } + } + } + else if (t.type () == token::t_call_expr) + { + // c-tor initializer + // + l = t.line (); + c = t.column (); + + ev = t.expression (); + et = expression::call_expr; + t = lexer_->next (); + } + + if (valid_ && !ev.empty ()) + { + expression& e (root_->new_node<expression> (*path_, l, c, et, ev)); + root_->new_edge<initialized> (*o, e); + } + + // option-def-trailer + // + if (t.punctuation () == token::p_lcbrace) + { + // doc-string-seq + // + for (t = lexer_->next ();; t = lexer_->next ()) + { + if (t.type () != token::t_string_lit) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "expected documentation string instead of " << t << endl; + throw error (); + } + + if (valid_) + o->doc ().push_back (doc_string (t.literal ().c_str (), + t.literal ().size ())); + + t = lexer_->next (); + + if (t.punctuation () != token::p_comma) + break; + } + + if (t.punctuation () != token::p_rcbrace) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "expected '}' instead of " << t << endl; + throw error (); + } + + t = lexer_->next (); + + // Allow semicolon after option-doc for backwards compatibility. + // + if (t.punctuation () == token::p_semi) + t = lexer_->next (); + } + else + { + if (t.punctuation () != token::p_semi) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "expected ';' instead of " << t << endl; + throw error (); + } + + t = lexer_->next (); + } + + return true; +} + +string parser:: +doc_string (const char* l, size_t n) +{ + // Pass 1: get rid of " (as in "foo""bar"), convert \" to just ". + // + string t1, t2, t3; + char p ('\0'); // Previous character. + + for (size_t i (0); i < n; ++i) + { + char c (l[i]); + + if (c == '"') + { + if (p == '\\') + { + t1[t1.size () - 1] = '"'; // Replace \ with ". + p = c; + } + continue; + } + + // We need to keep track of \\ escapings so we don't confuse them with \", + // as in \\". + // + if (c == '\\' && p == '\\') + p = '\0'; + else + p = c; + + t1 += c; + } + + // Pass two: get rid of leading and trailing spaces in each line. Also + // handle pre-formatted fragments. + // + if (t1.size () != 0) + { + bool more (true); + size_t b (0), e, p; + + bool pre (false); + size_t m (0); // Number of leading spaces to remove in pre. + + while (more) + { + p = e = t1.find ('\n', b); + + if (p == string::npos) + { + e = t1.size (); + more = false; + } + + if (b != e) // Unless this is just a single newline. + { + // In the pre mode we only remove up to m leading whitespaces. + // + { + size_t i (0); + while (b < e && + (t1[b] == 0x20 || t1[b] == 0x0D || t1[b] == 0x09) && + (!pre || i != m)) + { + ++b; + ++i; + } + + if (!pre) + m = i; + } + + --e; + while (e > b && (t1[e] == 0x20 || t1[e] == 0x0D || t1[e] == 0x09)) + --e; + + // Pre-formatted fragment marker or its escape. + // + if (t1[b] == '\\' && (b == e || (b + 1 == e && t1[e] == '\\'))) + { + // Use Start of Text (0x02) and End of Text (0x03) special + // characters as pre-formatted fragment markers. + // + if (b == e) + { + pre = !pre; + t2 += (pre ? 0x02 : 0x03); + } + else + t2 += "\\\\"; // Keep escaped. + } + else if (b <= e) + t2.append (t1, b, e - b + 1); + } + + if (more) + { + t2 += '\n'; + b = p + 1; + } + } + + if (pre) + { + cerr << *path_ << ": error: missing pre-formatted fragment end marker " + << "in documentation string '" << t1 << "'" << endl; + throw error (); + } + } + + // Pass 3: replace every single newline with single space and all multiple + // newlines (paragraph marker) with a single newline, unless we are in a + // pre-formatted fragment. Also process escapes in pre-formatted fragmens. + // + bool pre (false); + p = '\0'; // Previous character in pre-formatted fragment. + for (size_t i (0), n (t2.size ()); i < n; ++i) + { + char c (t2[i]); + + if (c == '\n' && !pre) + { + size_t j (i); + for (; i + 1 < n && t2[i + 1] == '\n'; ++i) ; + + if (j != 0 && i + 1 != n) // Strip leading and trailing newlines. + t3 += i != j ? '\n' : ' '; + } + else + { + if (c == (pre ? 0x03 : 0x02)) + { + pre = !pre; + + // Kill "inner" newlines (after opening and before closing '/' + // markers). Also check for "outer" newlines so that we always + // have paragraph separation. + // + size_t k (t3.size ()); + if (pre) + { + if (k != 0 && t3[k - 1] != '\n') // Outer. + { + cerr << *path_ << ": error: missing empty line before pre-" + << "formatted fragment start marker in documentation " + << "string '" << t1 << "'" << endl; + throw error (); + } + + ++i; // Skip inner. + } + else + { + if (t3[k - 1] == '\n') // Could be the same as opening if empty. + t3.resize (k - 1); // Pop inner. + + if (i + 2 < n && (t2[i + 1] != '\n' || t2[i + 2] != '\n')) // Outer. + { + cerr << *path_ << ": error: missing empty line after pre-" + << "formatted fragment end marker in documentation " + << "string '" << t1 << "'" << endl; + throw error (); + } + } + + t3 += c; + continue; + } + + if (pre) + { + // In the pre-formatted fragments the only two escapes that we + // recognize are \" which was handled on pass 1 above and \\ which we + // handle here. + // + if (c == '\\' && p == '\\') + { + p = '\0'; // Keep the already added and clear. + continue; + } + + p = c; + } + + t3 += c; + } + } + + return t3; +} + + +bool parser:: +qualified_name (token& t, string& r) +{ + if (t.type () != token::t_identifier && t.punctuation () != token::p_dcolon) + return false; + + r.clear (); + + if (t.punctuation () == token::p_dcolon) + { + r += "::"; + t = lexer_->next (); + } + + for (;;) + { + if (t.type () != token::t_identifier) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "expected identifier after '::'" << endl; + throw error (); + } + + r += t.identifier (); + t = lexer_->next (); + + if (t.type () == token::t_template_expr) + { + // Template-id. + // + r += t.expression (); + t = lexer_->next (); + } + + if (t.punctuation () == token::p_dcolon) + { + r += "::"; + t = lexer_->next (); + } + else + break; + } + + return true; +} + +bool parser:: +fundamental_type (token& t, string& r) +{ + r.clear (); + + switch (t.keyword ()) + { + case token::k_signed: + case token::k_unsigned: + { + r = t.keyword () == token::k_signed ? "signed" : "unsigned"; + switch ((t = lexer_->next ()).keyword ()) + { + case token::k_short: + { + r += " short"; + switch ((t = lexer_->next ()).keyword ()) + { + case token::k_int: + { + r += " int"; + t = lexer_->next (); + } + default: + break; + } + break; + } + case token::k_long: + { + r += " long"; + switch ((t = lexer_->next ()).keyword ()) + { + case token::k_int: + { + r += " int"; + t = lexer_->next (); + break; + } + case token::k_long: + { + r += " long"; + switch ((t = lexer_->next ()).keyword ()) + { + case token::k_int: + { + r += " int"; + t = lexer_->next (); + } + default: + break; + } + break; + } + default: + break; + } + break; + } + case token::k_int: + { + r += " int"; + switch ((t = lexer_->next ()).keyword ()) + { + case token::k_short: + { + r += " short"; + t = lexer_->next (); + break; + } + case token::k_long: + { + r += " long"; + switch ((t = lexer_->next ()).keyword ()) + { + case token::k_long: + { + r += " long"; + t = lexer_->next (); + } + default: + break; + } + break; + } + default: + break; + } + break; + } + case token::k_char: + { + r += " char"; + t = lexer_->next (); + break; + } + default: + break; + } + break; + } + case token::k_short: + case token::k_long: + { + bool l (t.keyword () == token::k_long); + r = l ? "long" : "short"; + + switch ((t = lexer_->next ()).keyword ()) + { + case token::k_signed: + case token::k_unsigned: + { + r += t.keyword () == token::k_signed ? " signed" : " unsigned"; + switch ((t = lexer_->next ()).keyword ()) + { + case token::k_int: + { + r += " int"; + t = lexer_->next (); + } + default: + break; + } + break; + } + case token::k_long: + { + r += " long"; + switch ((t = lexer_->next ()).keyword ()) + { + case token::k_signed: + case token::k_unsigned: + { + r += t.keyword () == token::k_signed ? " signed" : " unsigned"; + switch ((t = lexer_->next ()).keyword ()) + { + case token::k_int: + { + r += " int"; + t = lexer_->next (); + } + default: + break; + } + break; + } + case token::k_int: + { + r += " int"; + switch ((t = lexer_->next ()).keyword ()) + { + case token::k_signed: + { + r += " signed"; + t = lexer_->next (); + break; + } + case token::k_unsigned: + { + r += " unsigned"; + t = lexer_->next (); + break; + } + default: + break; + } + break; + } + default: + break; + } + break; + } + case token::k_int: + { + r += " int"; + switch ((t = lexer_->next ()).keyword ()) + { + case token::k_signed: + { + r += " signed"; + t = lexer_->next (); + break; + } + case token::k_unsigned: + { + r += " unsigned"; + t = lexer_->next (); + break; + } + default: + break; + } + break; + } + case token::k_double: + { + if (l) + { + r += " double"; + t = lexer_->next (); + } + break; + } + default: + break; + } + break; + } + case token::k_int: + { + r = "int"; + switch ((t = lexer_->next ()).keyword ()) + { + case token::k_signed: + case token::k_unsigned: + { + r += t.keyword () == token::k_signed ? " signed" : " unsigned"; + switch ((t = lexer_->next ()).keyword ()) + { + case token::k_short: + { + r += " short"; + t = lexer_->next (); + break; + } + case token::k_long: + { + r += " long"; + switch ((t = lexer_->next ()).keyword ()) + { + case token::k_long: + { + r += " long"; + t = lexer_->next (); + } + default: + break; + } + } + default: + break; + } + break; + } + case token::k_short: + { + r += " short"; + switch ((t = lexer_->next ()).keyword ()) + { + case token::k_signed: + { + r += " signed"; + t = lexer_->next (); + break; + } + case token::k_unsigned: + { + r += " unsigned"; + t = lexer_->next (); + break; + } + default: + break; + } + break; + } + case token::k_long: + { + r += " long"; + switch ((t = lexer_->next ()).keyword ()) + { + case token::k_signed: + { + r += " signed"; + t = lexer_->next (); + break; + } + case token::k_unsigned: + { + r += " unsigned"; + t = lexer_->next (); + break; + } + case token::k_long: + { + r += " long"; + switch ((t = lexer_->next ()).keyword ()) + { + case token::k_signed: + { + r += " signed"; + t = lexer_->next (); + break; + } + case token::k_unsigned: + { + r += " unsigned"; + t = lexer_->next (); + break; + } + default: + break; + } + break; + } + default: + break; + } + break; + } + default: + break; + } + break; + } + case token::k_char: + { + r = "char"; + switch ((t = lexer_->next ()).keyword ()) + { + case token::k_signed: + { + r += " signed"; + t = lexer_->next (); + break; + } + case token::k_unsigned: + { + r += " unsigned"; + t = lexer_->next (); + break; + } + default: + break; + } + break; + } + case token::k_bool: + { + r = "bool"; + t = lexer_->next (); + break; + } + case token::k_wchar: + { + r = "wchar_t"; + t = lexer_->next (); + break; + } + case token::k_float: + { + r = "float"; + t = lexer_->next (); + break; + } + case token::k_double: + { + r = "double"; + switch ((t = lexer_->next ()).keyword ()) + { + case token::k_long: + { + r += " long"; + t = lexer_->next (); + } + default: + break; + } + break; + } + default: + return false; + } + + return true; +} diff --git a/cli/cli/parser.hxx b/cli/cli/parser.hxx new file mode 100644 index 0000000..326768e --- /dev/null +++ b/cli/cli/parser.hxx @@ -0,0 +1,91 @@ +// file : cli/parser.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_PARSER_HXX +#define CLI_PARSER_HXX + +#include <map> +#include <string> +#include <vector> +#include <memory> // unique_ptr +#include <cstddef> // size_t +#include <istream> + +#include <cli/semantics/elements.hxx> +#include <cli/semantics/unit.hxx> + +class token; +class lexer; + +class parser +{ +public: + typedef std::vector<semantics::path> paths; + + parser (paths const& include_paths): include_paths_ (include_paths) {} + + struct invalid_input {}; + + std::unique_ptr<semantics::cli_unit> + parse (std::istream& is, semantics::path const& path); + +private: + struct error {}; + + void + def_unit (); + + void + source_decl (); + + void + include_decl (); + + bool + decl (token&); + + void + scope_doc (token&); + + void + namespace_def (); + + void + class_def (); + + bool + option_def (token&); + + std::string + doc_string (const char*, std::size_t); + + bool + qualified_name (token&, std::string& name); + + bool + fundamental_type (token&, std::string& name); + +private: + void + recover (token& t); + +private: + paths const include_paths_; + + bool valid_; + semantics::path const* path_; + + lexer* lexer_; + + semantics::cli_unit* root_; + semantics::cli_unit* cur_; + semantics::scope* scope_; + + std::size_t doc_count_; // Scope doc counter, see scope_doc() for details. + + typedef std::map<semantics::path, semantics::cli_unit*> include_map; + include_map include_map_; +}; + +#endif // CLI_PARSER_HXX diff --git a/cli/cli/parser.test.cxx b/cli/cli/parser.test.cxx new file mode 100644 index 0000000..6ef0d11 --- /dev/null +++ b/cli/cli/parser.test.cxx @@ -0,0 +1,45 @@ +// file : cli/parser.test.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <fstream> +#include <iostream> + +#include <cli/parser.hxx> +#include <cli/semantics.hxx> +#include <cli/traversal.hxx> + +using namespace std; + +int +main (int argc, char* argv[]) +{ + if (argc != 2) + { + cerr << "usage: " << argv[0] << " file.cli" << endl; + return 1; + } + + try + { + semantics::path path (argv[1]); + + ifstream ifs; + ifs.exceptions (ifstream::failbit | ifstream::badbit); + ifs.open (path.string ().c_str ()); + + parser::paths include_paths; + parser p (include_paths); + p.parse (ifs, path); + } + catch (semantics::invalid_path const& e) + { + cerr << "error: '" << e.path () << "' is not a valid filesystem path" + << endl; + return 1; + } + catch (parser::invalid_input const&) + { + return 1; + } +} diff --git a/cli/cli/parser.test.testscript b/cli/cli/parser.test.testscript new file mode 100644 index 0000000..9522dbe --- /dev/null +++ b/cli/cli/parser.test.testscript @@ -0,0 +1,242 @@ +# file : cli/parser.test.testscript +# license : MIT; see accompanying LICENSE file + +# @@ Give tests some meaningfull descriptions. Probably move c++-style comments +# from the test.cli files to test descriptions. +# + +: 000 +: +cat <<EOI >=test.cli; +// def-unit +// +include <string>; +namespace n {} +class c {}; +EOI +$* test.cli >:"" + +: 001 +: +cat <<EOI >=base.cli; +EOI +cat <<EOI >=common.cli; +include "base.cli"; +EOI +cat <<EOI >=test.cli; +// include-decl-seq, include-decl +// +include <string>; +include "types.hxx"; +include "common.cli"; +include "../001/base.cli"; +EOI +$* test.cli >:"" + +: 002 +: +cat <<EOI >=test.cli; +// namespace-def, namespace-def-body +// +namespace n1 {} + +namespace n1 +{ + namespace n2 {} + class c {}; + namespace n2 + { + namespace n3 {} + } +} + +namespace n4 {} +EOI +$* test.cli >:"" + +: 003 +: +cat <<EOI >=test.cli; +// class-def, inheritance-spec, abstract-spec +// +class c1 +{ +}; + +class c2 = 0 +{ +}; + +class c3: c1, ::c2 +{ +}; + +namespace n1 +{ + class c {}; +} + +class c4: n1::c = 0 +{ +}; + +EOI +$* test.cli >:"" + +: 004 +: +cat <<EOI >=test.cli; +// option-def-seq +// +class c +{ + bool -a; + int -b; + float -c; +}; +EOI +$* test.cli >:"" + +: 005 +: +cat <<EOI >=test.cli; +// option-def, type-spec, fundamental-type-spec, option-name-seq, +// option-name, initializer, initializer-expr +// +class c +{ + bool --bool; + char --char; + + int -i1; + unsigned int -i2; + int unsigned -i3; + long -i4; + long int -i5; + int long -i6; + unsigned long -i7; + long unsigned -i8; + + unsigned long int -i9; + long unsigned int -i10; + int long unsigned -i11; + unsigned int long -i12; + + short -i13; + unsigned short -i14; + short unsigned -i15; + + char -i16; + signed char -i17; + char signed -i18; + unsigned char -i19; + char unsigned -i20; + + long long -ll1; + long long int -ll2; + long long unsigned -ll3; + int long long -ll4; + unsigned long long -ll5; + long long int unsigned -ll6; + long long unsigned int -ll7; + unsigned long long int -ll8; + unsigned int long long -ll9; + int long long unsigned -ll10; + int unsigned long long -ll11; + + double -d1; + long double -d2; + double long -d3; + + foo -o1; + ::foo -o2; + ::foo<bar> -o3; + foo::bar -o4; + ::foo::bar -o5; + ::foo<bar>::baz -o6; + ::foo<bar>::baz< ::fox<2> > -o7; + + bool -n1|--name1|/name1; + bool "-n2"|"--name2"; + + string -init1 = "string"; + char -init2 = 'c'; + int -init3 = -5; + bool -inti4 = true; + long -init5 = (2 * 4 - 5); + type -init6 = type::default_value; + type -init7 (abc, 2 - 1); +}; +EOI +$* test.cli >:"" + +: 006 +: +cat <<EOI >=test.cli; +// option-doc +// +class c +{ + bool --help | -h {"Help me"}; + + int --comp = 5 + { + "<level>", + "Set compression level", + "Set compression level to <level>. + + The minimum value for this options is 0 and + maximum is 9." + }; +}; +EOI +$* test.cli >:"" + +: 007 +: +cat <<EOI >=base.cli; +class b1 {}; + +namespace n1 +{ + class b2 {}; + + namespace i1 + { + class b3 {}; + } +} +EOI +cat <<EOI >=test.cli; +// base class lookup +// + +include "base.cli"; + +class c1 {}; +class c2: c1 {}; +class c3: ::c1 {}; + +namespace n1 +{ + class c4 {}; + class c5: c4 {}; + class c6: n1::c4 {}; + class c7: ::n1::c4 {}; + + class c8: b2 {}; // From included. + class c9: i1::b3 {}; // From included. + + namespace i1 + { + class c10: c4 {}; // Outer scope. + class c11: b3 {}; // From included. + class c12: b2 {}; // Outer scope from included. + class c4: n1::c4 {}; // Qualified name from outer scope. + } +} + +class c13: n1::c4 {}; +class c14: ::n1::c4 {}; +EOI +$* test.cli >:"" diff --git a/cli/cli/runtime-header.cxx b/cli/cli/runtime-header.cxx new file mode 100644 index 0000000..adf70dd --- /dev/null +++ b/cli/cli/runtime-header.cxx @@ -0,0 +1,647 @@ +// file : cli/runtime-header.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <cli/runtime-header.hxx> + +using namespace std; + +void +generate_runtime_header (context& ctx) +{ + ostream& os (ctx.os); + + if (ctx.options.generate_file_scanner ()) + os << "#include <list>" << endl + << "#include <deque>" << endl; + + if (ctx.options.generate_description ()) + os << "#include <map>" << endl; + + if (ctx.options.generate_description () || + ctx.options.generate_vector_scanner ()) + os << "#include <vector>" << endl; + + os << "#include <iosfwd>" << endl + << "#include <string>" << endl + << "#include <cstddef>" << endl + << "#include <exception>" << endl + << endl; + + // VC++ and xlC don't like the (void)x expression if x is a reference + // to an incomplete type. On the other hand, GCC warns that (void*)&x + // doesn't have any effect. + // + os << "#ifndef CLI_POTENTIALLY_UNUSED" << endl + << "# if defined(_MSC_VER) || defined(__xlC__)" << endl + << "# define CLI_POTENTIALLY_UNUSED(x) (void*)&x" << endl + << "# else" << endl + << "# define CLI_POTENTIALLY_UNUSED(x) (void)x" << endl + << "# endif" << endl + << "#endif" << endl + << endl; + + ctx.ns_open (ctx.cli); + + // usage_para + // + if (!ctx.options.suppress_usage ()) + os << "class usage_para" + << "{" + << "public:" << endl + << "enum value" + << "{" + << "none," << endl + << "text," << endl + << "option" << endl + << "};" + << "usage_para (value);" + << endl + << "operator value () const {return v_;}" // Can't generate outside. + << "private:" << endl + << "value v_;" + << "};"; + + // unknown_mode + // + os << "class unknown_mode" + << "{" + << "public:" << endl + << "enum value" + << "{" + << "skip," << endl + << "stop," << endl + << "fail" << endl + << "};" + << "unknown_mode (value);" + << endl + << "operator value () const {return v_;}" // Can't generate outside. + << "private:" << endl + << "value v_;" + << "};"; + + // Exceptions. + // + + string const& os_type (ctx.options.ostream_type ()); + + os << "// Exceptions." << endl + << "//" << endl + << endl; + + os << "class exception: public std::exception" + << "{" + << "public:" << endl + << "virtual void" << endl + << "print (" << os_type << "&) const = 0;" + << "};"; + + os << os_type << "&" << endl + << "operator<< (" << os_type << "&, const exception&);" + << endl; + + os << "class unknown_option: public exception" + << "{" + << "public:" << endl + << "virtual" << endl + << "~unknown_option () throw ();" + << endl + << "unknown_option (const std::string& option);" + << endl + << "const std::string&" << endl + << "option () const;" + << endl + << "virtual void" << endl + << "print (" << os_type << "&) const;" + << endl + << "virtual const char*" << endl + << "what () const throw ();" + << endl + << "private:" << endl + << "std::string option_;" + << "};"; + + os << "class unknown_argument: public exception" + << "{" + << "public:" << endl + << "virtual" << endl + << "~unknown_argument () throw ();" + << endl + << "unknown_argument (const std::string& argument);" + << endl + << "const std::string&" << endl + << "argument () const;" + << endl + << "virtual void" << endl + << "print (" << os_type << "&) const;" + << endl + << "virtual const char*" << endl + << "what () const throw ();" + << endl + << "private:" << endl + << "std::string argument_;" + << "};"; + + os << "class missing_value: public exception" + << "{" + << "public:" << endl + << "virtual" << endl + << "~missing_value () throw ();" + << endl + << "missing_value (const std::string& option);" + << endl + << "const std::string&" << endl + << "option () const;" + << endl + << "virtual void" << endl + << "print (" << os_type << "&) const;" + << endl + << "virtual const char*" << endl + << "what () const throw ();" + << endl + << "private:" << endl + << "std::string option_;" + << "};"; + + os << "class invalid_value: public exception" + << "{" + << "public:" << endl + << "virtual" << endl + << "~invalid_value () throw ();" + << endl + << "invalid_value (const std::string& option," << endl + << "const std::string& value," << endl + << "const std::string& message = std::string ());" + << endl + << "const std::string&" << endl + << "option () const;" + << endl + << "const std::string&" << endl + << "value () const;" + << endl + << "const std::string&" << endl + << "message () const;" + << endl + << "virtual void" << endl + << "print (" << os_type << "&) const;" + << endl + << "virtual const char*" << endl + << "what () const throw ();" + << endl + << "private:" << endl + << "std::string option_;" + << "std::string value_;" + << "std::string message_;" + << "};"; + + os << "class eos_reached: public exception" + << "{" + << "public:" << endl + << "virtual void" << endl + << "print (" << os_type << "&) const;" + << endl + << "virtual const char*" << endl + << "what () const throw ();" + << "};"; + + if (ctx.options.generate_file_scanner ()) + { + os << "class file_io_failure: public exception" + << "{" + << "public:" << endl + << "virtual" << endl + << "~file_io_failure () throw ();" + << endl + << "file_io_failure (const std::string& file);" + << endl + << "const std::string&" << endl + << "file () const;" + << endl + << "virtual void" << endl + << "print (" << os_type << "&) const;" + << endl + << "virtual const char*" << endl + << "what () const throw ();" + << endl + << "private:" << endl + << "std::string file_;" + << "};"; + + os << "class unmatched_quote: public exception" + << "{" + << "public:" << endl + << "virtual" << endl + << "~unmatched_quote () throw ();" + << endl + << "unmatched_quote (const std::string& argument);" + << endl + << "const std::string&" << endl + << "argument () const;" + << endl + << "virtual void" << endl + << "print (" << os_type << "&) const;" + << endl + << "virtual const char*" << endl + << "what () const throw ();" + << endl + << "private:" << endl + << "std::string argument_;" + << "};"; + } + + if (ctx.options.generate_group_scanner ()) + { + os << "class unexpected_group: public exception" + << "{" + << "public:" << endl + << "virtual" << endl + << "~unexpected_group () throw ();" + << endl + << "unexpected_group (const std::string& argument," << endl + << "const std::string& group);" + << endl + << "const std::string&" << endl + << "argument () const;" + << endl + << "const std::string&" << endl + << "group () const;" + << endl + << "virtual void" << endl + << "print (std::ostream&) const;" + << endl + << "virtual const char*" << endl + << "what () const throw ();" + << endl + << "private:" << endl + << "std::string argument_;" + << "std::string group_;" + << "};"; + + os << "class group_separator: public exception" << endl + << "{" + << "public:" << endl + << "virtual" << endl + << "~group_separator () throw ();" + << endl + << "// Note: either (but not both) can be empty." << endl + << "//" << endl + << "group_separator (const std::string& encountered," << endl + << "const std::string& expected);" + << endl + << "const std::string&" << endl + << "encountered () const;" + << endl + << "const std::string&" << endl + << "expected () const;" + << endl + << "virtual void" << endl + << "print (std::ostream&) const;" + << endl + << "virtual const char*" << endl + << "what () const throw ();" + << endl + << "private:" << endl + << "std::string encountered_;" + << "std::string expected_;" + << "};"; + } + + // scanner + // + os << "// Command line argument scanner interface." << endl + << "//" << endl + << "// The values returned by next() are guaranteed to be valid" << endl + << "// for the two previous arguments up until a call to a third" << endl + << "// peek() or next()." << endl + << "//" << endl + << "class scanner" + << "{" + << "public:" << endl + << "virtual" << endl + << "~scanner ();" + << endl + << "virtual bool" << endl + << "more () = 0;" + << endl + << "virtual const char*" << endl + << "peek () = 0;" + << endl + << "virtual const char*" << endl + << "next () = 0;" + << endl + << "virtual void" << endl + << "skip () = 0;" + << "};"; + + // argv_scanner + // + os << "class argv_scanner: public scanner" + << "{" + << "public:" << endl + << "argv_scanner (int& argc, char** argv, bool erase = false);" + << "argv_scanner (int start, int& argc, char** argv, bool erase = false);" + << endl + << "int" << endl + << "end () const;" + << endl + << "virtual bool" << endl + << "more ();" + << endl + << "virtual const char*" << endl + << "peek ();" + << endl + << "virtual const char*" << endl + << "next ();" + << endl + << "virtual void" << endl + << "skip ();" + << endl + << "private:" << endl + << "int i_;" + << "int& argc_;" + << "char** argv_;" + << "bool erase_;" + << "};"; + + // vector_scanner + // + if (ctx.options.generate_vector_scanner ()) + { + os << "class vector_scanner: public scanner" + << "{" + << "public:" << endl + << "vector_scanner (const std::vector<std::string>&, " << + "std::size_t start = 0);" + << endl + << "std::size_t" << endl + << "end () const;" + << endl + << "void" << endl + << "reset (std::size_t start = 0);" + << endl + << "virtual bool" << endl + << "more ();" + << endl + << "virtual const char*" << endl + << "peek ();" + << endl + << "virtual const char*" << endl + << "next ();" + << endl + << "virtual void" << endl + << "skip ();" + << endl + << "private:" << endl + << "const std::vector<std::string>& v_;" + << "std::size_t i_;" + << "};"; + } + + // argv_file_scanner + // + if (ctx.options.generate_file_scanner ()) + { + os << "class argv_file_scanner: public argv_scanner" + << "{" + << "public:" << endl + << "argv_file_scanner (int& argc," << endl + << "char** argv," << endl + << "const std::string& option," << endl + << "bool erase = false);" + << endl + << "argv_file_scanner (int start," << endl + << "int& argc," << endl + << "char** argv," << endl + << "const std::string& option," << endl + << "bool erase = false);" + << endl + << "argv_file_scanner (const std::string& file," << endl + << "const std::string& option);" + << endl + << "struct option_info" + << "{" + << "// If search_func is not NULL, it is called, with the arg" << endl + << "// value as the second argument, to locate the options file." << endl + << "// If it returns an empty string, then the file is ignored." << endl + << "//" << endl + << "const char* option;" + << "std::string (*search_func) (const char*, void* arg);" + << "void* arg;" + << "};" + << "argv_file_scanner (int& argc," << endl + << "char** argv," << endl + << "const option_info* options," << endl + << "std::size_t options_count," << endl + << "bool erase = false);" + << endl + << "argv_file_scanner (int start," << endl + << "int& argc," << endl + << "char** argv," << endl + << "const option_info* options," << endl + << "std::size_t options_count," << endl + << "bool erase = false);" + << endl + << "argv_file_scanner (const std::string& file," << endl + << "const option_info* options = 0," << endl + << "std::size_t options_count = 0);" + << endl + << "virtual bool" << endl + << "more ();" + << endl + << "virtual const char*" << endl + << "peek ();" + << endl + << "virtual const char*" << endl + << "next ();" + << endl + << "virtual void" << endl + << "skip ();" + << endl + << "// Return the file path if the peeked at argument came from a file and" << endl + << "// the empty string otherwise. The reference is guaranteed to be valid" << endl + << "// till the end of the scanner lifetime." << endl + << "//" << endl + << "const std::string&" << endl + << "peek_file ();" + << endl + << "// Return the 1-based line number if the peeked at argument came from" << endl + << "// a file and zero otherwise." << endl + << "//" << endl + << "std::size_t" << endl + << "peek_line ();" + << endl + << "private:" << endl + << "const option_info*" << endl + << "find (const char*) const;" + << endl + << "void" << endl + << "load (const std::string& file);" + << endl + << "typedef argv_scanner base;" + << endl + << "const std::string option_;" + << "option_info option_info_;" + << "const option_info* options_;" + << "std::size_t options_count_;" + << endl + << "struct arg" + << "{" + << "std::string value;" + << "const std::string* file;" + << "std::size_t line;" + << "};" + << "std::deque<arg> args_;" + << "std::list<std::string> files_;" + << endl + << "// Circular buffer of two arguments." << endl + << "//" << endl + << "std::string hold_[2];" + << "std::size_t i_;"; + + if (!ctx.opt_sep.empty ()) + os << endl + << "bool skip_;"; + + os << endl + << "static int zero_argc_;" + << "static std::string empty_string_;" + << "};"; + } + + // group_scanner + // + if (ctx.options.generate_group_scanner ()) + { + os << "class group_scanner: public scanner" + << "{" + << "public:" << endl + << "group_scanner (scanner&);" + << endl + << "virtual bool" << endl + << "more ();" + << endl + << "virtual const char*" << endl + << "peek ();" + << endl + << "virtual const char*" << endl + << "next ();" + << endl + << "virtual void" << endl + << "skip ();" + << endl + << "// The group is only available after the call to next()" << endl + << "// (and skip() -- in case one needs to make sure the group" << endl + << "// was empty, or some such) and is only valid (and must be" << endl + << "// handled) until the next call to any of the scanner" << endl + << "// functions (including more())." << endl + << "//" << endl + << "scanner&" << endl + << "group ();" + << endl + << "// Escape an argument that is a group separator. Return the" << endl + << "// passed string if no escaping is required." << endl + << "//" << endl + << "static const char*" << endl + << "escape (const char*);" + << endl + << "private:" << endl + << "enum state" + << "{" + << "peeked, // Argument peeked at with peek()." << endl + << "scanned, // Argument scanned with next()." << endl + << "skipped, // Argument skipped with skip()/initial." << endl + << "};" + << "enum separator" + << "{" + << "none," << endl + << "open, // {" << endl + << "close, // }" << endl + << "open_plus, // +{" << endl + << "close_plus // }+" << endl + << "};" + << "static separator" << endl + << "sense (const char*);" + << endl + << "// If the state is scanned or skipped, then scan the" << endl + << "// leading groups and save the next (unescaped) argument in" << endl + << "// arg_. If the state is peeked, then scan the trailing" << endl + << "// groups. In both cases set the new state." << endl + << "//" << endl + << "void" << endl + << "scan_group (state);" + << endl + << "scanner& scan_;" + << "state state_;" + << endl + << "// Circular buffer of two arguments." << endl + << "//" << endl + << "std::string arg_[2];" + << "std::size_t i_;" + << endl + << "std::vector<std::string> group_;" + << "vector_scanner group_scan_;" + << "};"; + } + + // Option description. + // + if (ctx.options.generate_description ()) + { + os << "typedef std::vector<std::string> option_names;" + << endl; + + os << "class option" + << "{" + << "public:" << endl + << endl + << "const std::string&" << endl + << "name () const;" + << endl + << "const option_names&" << endl + << "aliases () const;" + << endl + << "bool" << endl + << "flag () const;" + << endl + << "const std::string&" << endl + << "default_value () const;" + << endl + << "public:" + << "option ();" + << "option (const std::string& name," << endl + << "const option_names& aliases," << endl + << "bool flag," << endl + << "const std::string& default_value);" + << endl + << "private:" << endl + << "std::string name_;" + << "option_names aliases_;" + << "bool flag_;" + << "std::string default_value_;" + << "};"; + + os << "class options: public std::vector<option>" + << "{" + << "public:" << endl + << "typedef std::vector<option> container_type;" + << endl + << "container_type::const_iterator" << endl + << "find (const std::string& name) const;" + << endl + << "void" << endl + << "push_back (const option&);" + << "private:" << endl + << "typedef std::map<std::string, container_type::size_type> map_type;" + << "map_type map_;" + << "};"; + } + + // Parser class template. Provide a forward declaration to allow + // custom specializations. + // + os << "template <typename X>" << endl + << "struct parser;" + << endl; + + ctx.ns_close (ctx.cli); +} diff --git a/cli/cli/runtime-header.hxx b/cli/cli/runtime-header.hxx new file mode 100644 index 0000000..f4bff7c --- /dev/null +++ b/cli/cli/runtime-header.hxx @@ -0,0 +1,13 @@ +// file : cli/runtime-header.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_RUNTIME_HEADER_HXX +#define CLI_RUNTIME_HEADER_HXX + +#include <cli/context.hxx> + +void +generate_runtime_header (context&); + +#endif // CLI_RUNTIME_HEADER_HXX diff --git a/cli/cli/runtime-inline.cxx b/cli/cli/runtime-inline.cxx new file mode 100644 index 0000000..8f0e84c --- /dev/null +++ b/cli/cli/runtime-inline.cxx @@ -0,0 +1,508 @@ +// file : cli/runtime-inline.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <cli/runtime-inline.hxx> + +using namespace std; + +void +generate_runtime_inline (context& ctx) +{ + ostream& os (ctx.os); + + os << "#include <cassert>" << endl + << endl; + + string const& inl (ctx.inl); + string const& os_type (ctx.options.ostream_type ()); + + ctx.ns_open (ctx.cli); + + // usage_para + // + if (!ctx.options.suppress_usage ()) + os << "// usage_para" << endl + << "//" << endl + + << inl << "usage_para::" << endl + << "usage_para (value v)" << endl + << ": v_ (v)" + << "{" + << "}"; + + // unknown_mode + // + os << "// unknown_mode" << endl + << "//" << endl + + << inl << "unknown_mode::" << endl + << "unknown_mode (value v)" << endl + << ": v_ (v)" + << "{" + << "}"; + + // exception + // + os << "// exception" << endl + << "//" << endl + + << inl << os_type << "&" << endl + << "operator<< (" << os_type << "& os, const exception& e)" + << "{" + << "e.print (os);" + << "return os;" + << "}"; + + // unknown_option + // + os << "// unknown_option" << endl + << "//" << endl + + << inl << "unknown_option::" << endl + << "unknown_option (const std::string& option)" << endl + << ": option_ (option)" + << "{" + << "}" + + << inl << "const std::string& unknown_option::" << endl + << "option () const" + << "{" + << "return option_;" + << "}"; + + // unknown_argument + // + os << "// unknown_argument" << endl + << "//" << endl + + << inl << "unknown_argument::" << endl + << "unknown_argument (const std::string& argument)" << endl + << ": argument_ (argument)" + << "{" + << "}" + + << inl << "const std::string& unknown_argument::" << endl + << "argument () const" + << "{" + << "return argument_;" + << "}"; + + // missing_value + // + os << "// missing_value" << endl + << "//" << endl + + << inl << "missing_value::" << endl + << "missing_value (const std::string& option)" << endl + << ": option_ (option)" + << "{" + << "}" + + << inl << "const std::string& missing_value::" << endl + << "option () const" + << "{" + << "return option_;" + << "}"; + + // invalid_value + // + os << "// invalid_value" << endl + << "//" << endl + + << inl << "invalid_value::" << endl + << "invalid_value (const std::string& option," << endl + << "const std::string& value," << endl + << "const std::string& message)" << endl + << ": option_ (option)," << endl + << " value_ (value)," << endl + << " message_ (message)" + << "{" + << "}" + + << inl << "const std::string& invalid_value::" << endl + << "option () const" + << "{" + << "return option_;" + << "}" + + << inl << "const std::string& invalid_value::" << endl + << "value () const" + << "{" + << "return value_;" + << "}" + + << inl << "const std::string& invalid_value::" << endl + << "message () const" + << "{" + << "return message_;" + << "}"; + + if (ctx.options.generate_file_scanner ()) + { + // file_io_failure + // + os << "// file_io_failure" << endl + << "//" << endl + + << inl << "file_io_failure::" << endl + << "file_io_failure (const std::string& file)" << endl + << ": file_ (file)" + << "{" + << "}" + + << inl << "const std::string& file_io_failure::" << endl + << "file () const" + << "{" + << "return file_;" + << "}"; + + // unmatched_option + // + os << "// unmatched_quote" << endl + << "//" << endl + + << inl << "unmatched_quote::" << endl + << "unmatched_quote (const std::string& argument)" << endl + << ": argument_ (argument)" + << "{" + << "}" + + << inl << "const std::string& unmatched_quote::" << endl + << "argument () const" + << "{" + << "return argument_;" + << "}"; + } + + if (ctx.options.generate_group_scanner ()) + { + // unexpected_group + // + os << "// unexpected_group" << endl + << "//" << endl + + << inl << "unexpected_group::" << endl + << "unexpected_group (const std::string& argument," << endl + << "const std::string& group)" << endl + << ": argument_ (argument), group_ (group)" + << "{" + << "}" + + << inl << "const std::string& unexpected_group::" << endl + << "argument () const" + << "{" + << "return argument_;" + << "}" + + << inl << "const std::string& unexpected_group::" << endl + << "group () const" + << "{" + << "return group_;" + << "}"; + + // group_separator + // + os << "// group_separator" << endl + << "//" << endl + + << inl << "group_separator::" << endl + << "group_separator (const std::string& encountered," << endl + << "const std::string& expected)" << endl + << ": encountered_ (encountered), expected_ (expected)" + << "{" + << "}" + + << inl << "const std::string& group_separator::" << endl + << "encountered () const" + << "{" + << "return encountered_;" + << "}" + + << inl << "const std::string& group_separator::" << endl + << "expected () const" + << "{" + << "return expected_;" + << "}"; + } + + // argv_scanner + // + os << "// argv_scanner" << endl + << "//" << endl; + + os << inl << "argv_scanner::" << endl + << "argv_scanner (int& argc, char** argv, bool erase)" << endl + << ": i_ (1), argc_ (argc), argv_ (argv), erase_ (erase)" + << "{" + << "}"; + + os << inl << "argv_scanner::" << endl + << "argv_scanner (int start, int& argc, char** argv, bool erase)" << endl + << ": i_ (start), argc_ (argc), argv_ (argv), erase_ (erase)" + << "{" + << "}"; + + os << inl << "int argv_scanner::" << endl + << "end () const" + << "{" + << "return i_;" + << "}"; + + // vector_scanner + // + if (ctx.options.generate_vector_scanner ()) + { + os << "// vector_scanner" << endl + << "//" << endl; + + os << inl << "vector_scanner::" << endl + << "vector_scanner (const std::vector<std::string>& v, " << + "std::size_t i)" << endl + << ": v_ (v), i_ (i)" + << "{" + << "}"; + + os << inl << "std::size_t vector_scanner::" << endl + << "end () const" + << "{" + << "return i_;" + << "}"; + + os << inl << "void vector_scanner::" << endl + << "reset (std::size_t i)" + << "{" + << "i_ = i;" + << "}"; + } + + // argv_file_scanner + // + if (ctx.options.generate_file_scanner ()) + { + bool sep (!ctx.opt_sep.empty ()); + + os << "// argv_file_scanner" << endl + << "//" << endl; + + os << inl << "argv_file_scanner::" << endl + << "argv_file_scanner (int& argc," << endl + << "char** argv," << endl + << "const std::string& option," << endl + << "bool erase)" << endl + << ": argv_scanner (argc, argv, erase)," << endl + << " option_ (option)," << endl + << " options_ (&option_info_)," << endl + << " options_count_ (1)," << endl + << " i_ (1)"; + if (sep) + os << "," << endl + << " skip_ (false)"; + os << "{" + << "option_info_.option = option_.c_str ();" + << "option_info_.search_func = 0;" + << "}"; + + os << inl << "argv_file_scanner::" << endl + << "argv_file_scanner (int start," << endl + << "int& argc," << endl + << "char** argv," << endl + << "const std::string& option," << endl + << "bool erase)" << endl + << ": argv_scanner (start, argc, argv, erase)," << endl + << " option_ (option)," << endl + << " options_ (&option_info_)," << endl + << " options_count_ (1)," << endl + << " i_ (1)"; + if (sep) + os << "," << endl + << " skip_ (false)"; + os << "{" + << "option_info_.option = option_.c_str ();" + << "option_info_.search_func = 0;" + << "}"; + + os << inl << "argv_file_scanner::" << endl + << "argv_file_scanner (const std::string& file," << endl + << "const std::string& option)" << endl + << ": argv_scanner (0, zero_argc_, 0)," << endl + << " option_ (option)," << endl + << " options_ (&option_info_)," << endl + << " options_count_ (1)," << endl + << " i_ (1)"; + if (sep) + os << "," << endl + << " skip_ (false)"; + os << "{" + << "option_info_.option = option_.c_str ();" + << "option_info_.search_func = 0;" + << endl + << "load (file);" + << "}"; + + os << inl << "argv_file_scanner::" << endl + << "argv_file_scanner (int& argc," << endl + << "char** argv," << endl + << "const option_info* options," << endl + << "std::size_t options_count," << endl + << "bool erase)" << endl + << ": argv_scanner (argc, argv, erase)," << endl + << " options_ (options)," << endl + << " options_count_ (options_count)," << endl + << " i_ (1)"; + if (sep) + os << "," << endl + << " skip_ (false)"; + os << "{" + << "}"; + + os << inl << "argv_file_scanner::" << endl + << "argv_file_scanner (int start," << endl + << "int& argc," << endl + << "char** argv," << endl + << "const option_info* options," << endl + << "std::size_t options_count," << endl + << "bool erase)" << endl + << ": argv_scanner (start, argc, argv, erase)," << endl + << " options_ (options)," << endl + << " options_count_ (options_count)," << endl + << " i_ (1)"; + if (sep) + os << "," << endl + << " skip_ (false)"; + os << "{" + << "}"; + + os << inl << "argv_file_scanner::" << endl + << "argv_file_scanner (const std::string& file," << endl + << "const option_info* options," << endl + << "std::size_t options_count)" << endl + << ": argv_scanner (0, zero_argc_, 0)," << endl + << " options_ (options)," << endl + << " options_count_ (options_count)," << endl + << " i_ (1)"; + if (sep) + os << "," << endl + << " skip_ (false)"; + os << "{" + << "load (file);" + << "}"; + } + + // group_scanner + // + if (ctx.options.generate_group_scanner ()) + { + os << "// group_scanner" << endl + << "//" << endl + + << inl << "group_scanner::" << endl + << "group_scanner (scanner& s)" << endl + << ": scan_ (s), state_ (skipped), i_ (1), group_scan_ (group_)" + << "{" + << "}" + + << inl << "scanner& group_scanner::" << endl + << "group ()" + << "{" + << "assert (state_ == scanned || state_ == skipped);" + << "return group_scan_;" + << "}" + + << inl << "const char* group_scanner::" << endl + << "escape (const char* a)" + << "{" + << "switch (sense (a))" + << "{" + << "case separator::none: break;" + << "case separator::open: return \"\\\\{\";" + << "case separator::close: return \"\\\\}\";" + << "case separator::open_plus: return \"\\\\+{\";" + << "case separator::close_plus: return \"\\\\}+\";" + << "}" + << "return a;" + << "}" + + << inl << "group_scanner::separator group_scanner::" << endl + << "sense (const char* s)" + << "{" + << "switch (s[0])" + << "{" + << "case '{': return s[1] == '\\0' ? open : none;" + << "case '}':" + << "{" + << "switch (s[1])" + << "{" + << "case '+': return s[2] == '\\0' ? close_plus : none;" + << "default: return s[1] == '\\0' ? close : none;" + << "}" + << "}" + << "case '+':" + << "{" + << "switch (s[1])" + << "{" + << "case '{': return s[2] == '\\0' ? open_plus : none;" + << "default: return none;" + << "}" + << "}" + << "}" + << "return none;" + << "}"; + } + + // Option description. + // + if (ctx.options.generate_description ()) + { + // option + // + os << inl << "const std::string& option::" << endl + << "name () const" + << "{" + << "return name_;" + << "}"; + + os << inl << "const option_names& option::" << endl + << "aliases () const" + << "{" + << "return aliases_;" + << "}"; + + os << inl << "bool option::" << endl + << "flag () const" + << "{" + << "return flag_;" + << "}"; + + os << inl << "const std::string& option::" << endl + << "default_value () const" + << "{" + << "return default_value_;" + << "}"; + + os << inl << "option::" << endl + << "option ()" + << "{" + << "}"; + + os << inl << "option::" << endl + << "option (const std::string& n," << endl + << "const option_names& a," << endl + << "bool f," << endl + << "const std::string& dv)" << endl + << ": name_ (n), aliases_ (a), flag_ (f), default_value_ (dv)" + << "{" + << "}"; + + // options + // + os << inl << "options::container_type::const_iterator options::" << endl + << "find (const std::string& name) const" + << "{" + << "map_type::const_iterator i (map_.find (name));" + << "return i != map_.end () ? begin () + i->second : end ();" + << "}"; + } + + ctx.ns_close (ctx.cli); +} diff --git a/cli/cli/runtime-inline.hxx b/cli/cli/runtime-inline.hxx new file mode 100644 index 0000000..045c060 --- /dev/null +++ b/cli/cli/runtime-inline.hxx @@ -0,0 +1,13 @@ +// file : cli/runtime-inline.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_RUNTIME_INLINE_HXX +#define CLI_RUNTIME_INLINE_HXX + +#include <cli/context.hxx> + +void +generate_runtime_inline (context&); + +#endif // CLI_RUNTIME_INLINE_HXX diff --git a/cli/cli/runtime-source.cxx b/cli/cli/runtime-source.cxx new file mode 100644 index 0000000..36d8968 --- /dev/null +++ b/cli/cli/runtime-source.cxx @@ -0,0 +1,1052 @@ +// file : cli/runtime-source.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <cli/runtime-source.hxx> + +using namespace std; + +void +generate_runtime_source (context& ctx, bool complete) +{ + ostream& os (ctx.os); + + os << "#include <map>" << endl + << "#include <set>" << endl + << "#include <string>" << endl + << "#include <vector>" << endl + << "#include <ostream>" << endl + << "#include <sstream>" << endl; + + if (complete && ctx.options.generate_file_scanner ()) + os << "#include <cstring>" << endl + << "#include <fstream>" << endl; + + os << endl; + + ctx.ns_open (ctx.cli); + + if (complete) + { + string const& os_type (ctx.options.ostream_type ()); + + // unknown_option + // + os << "// unknown_option" << endl + << "//" << endl + << "unknown_option::" << endl + << "~unknown_option () throw ()" + << "{" + << "}" + + << "void unknown_option::" << endl + << "print (" << os_type << "& os) const" + << "{" + << "os << \"unknown option '\" << option ().c_str () << \"'\";" + << "}" + + << "const char* unknown_option::" << endl + << "what () const throw ()" + << "{" + << "return \"unknown option\";" + << "}"; + + // unknown_argument + // + os << "// unknown_argument" << endl + << "//" << endl + << "unknown_argument::" << endl + << "~unknown_argument () throw ()" + << "{" + << "}" + + << "void unknown_argument::" << endl + << "print (" << os_type << "& os) const" + << "{" + << "os << \"unknown argument '\" << argument ().c_str () << \"'\";" + << "}" + + << "const char* unknown_argument::" << endl + << "what () const throw ()" + << "{" + << "return \"unknown argument\";" + << "}"; + + // missing_value + // + os << "// missing_value" << endl + << "//" << endl + << "missing_value::" << endl + << "~missing_value () throw ()" + << "{" + << "}" + + << "void missing_value::" << endl + << "print (" << os_type << "& os) const" + << "{" + << "os << \"missing value for option '\" << option ().c_str () << \"'\";" + << "}" + + << "const char* missing_value::" << endl + << "what () const throw ()" + << "{" + << "return \"missing option value\";" + << "}"; + + // invalid_value + // + os << "// invalid_value" << endl + << "//" << endl + << "invalid_value::" << endl + << "~invalid_value () throw ()" + << "{" + << "}" + + << "void invalid_value::" << endl + << "print (" << os_type << "& os) const" + << "{" + << "os << \"invalid value '\" << value ().c_str () << \"' for " << + "option '\"" << endl + << " << option ().c_str () << \"'\";" + << endl + << "if (!message ().empty ())" << endl + << "os << \": \" << message ().c_str ();" + << "}" + + << "const char* invalid_value::" << endl + << "what () const throw ()" + << "{" + << "return \"invalid option value\";" + << "}"; + + // eos_reached + // + os << "// eos_reached" << endl + << "//" << endl + << "void eos_reached::" << endl + << "print (" << os_type << "& os) const" + << "{" + << "os << what ();" + << "}" + + << "const char* eos_reached::" << endl + << "what () const throw ()" + << "{" + << "return \"end of argument stream reached\";" + << "}"; + + if (ctx.options.generate_file_scanner ()) + { + // file_io_failure + // + os << "// file_io_failure" << endl + << "//" << endl + << "file_io_failure::" << endl + << "~file_io_failure () throw ()" + << "{" + << "}" + + << "void file_io_failure::" << endl + << "print (" << os_type << "& os) const" + << "{" + << "os << \"unable to open file '\" << file ().c_str () << " << + "\"' or read failure\";" + << "}" + + << "const char* file_io_failure::" << endl + << "what () const throw ()" + << "{" + << "return \"unable to open file or read failure\";" + << "}"; + + // unmatched_argument + // + os << "// unmatched_quote" << endl + << "//" << endl + << "unmatched_quote::" << endl + << "~unmatched_quote () throw ()" + << "{" + << "}" + + << "void unmatched_quote::" << endl + << "print (" << os_type << "& os) const" + << "{" + << "os << \"unmatched quote in argument '\" << " << + "argument ().c_str () << \"'\";" + << "}" + + << "const char* unmatched_quote::" << endl + << "what () const throw ()" + << "{" + << "return \"unmatched quote\";" + << "}"; + } + + if (ctx.options.generate_group_scanner ()) + { + // unexpected_group + // + os << "// unexpected_group" << endl + << "//" << endl + + << "unexpected_group::" << endl + << "~unexpected_group () throw ()" + << "{" + << "}" + + << "void unexpected_group::" << endl + << "print (" << os_type << "& os) const" + << "{" + << "os << \"unexpected grouped argument '\" << group_ << \"' \"" << endl + << " << \"for argument '\" << argument_ << \"'\";" + << "}" + + << "const char* unexpected_group::" << endl + << "what () const throw ()" + << "{" + << "return \"unexpected grouped argument\";" + << "}"; + + // group_separator + // + os << "// group_separator" << endl + << "//" << endl + + << "group_separator::" << endl + << "~group_separator () throw ()" + << "{" + << "}" + + << "void group_separator::" << endl + << "print (" << os_type << "& os) const" + << "{" + << "bool ex (!expected_.empty ());" + << "bool en (!encountered_.empty ());" + << endl + << "if (ex)" + << "{" + << "os << \"expected group separator '\" << expected_ << \"'\";" + << "if (en)" << endl + << "os << \" instead of '\" << encountered_ << \"'\";" + << "}" + << "else" << endl + << "os << \"unexpected group separator '\" << encountered_ << \"'\";" + << endl + << "if (en)" << endl + << "os << \", use '\\\\\" << encountered_ << \"' to escape\";" + << "}" + + << "const char* group_separator::" << endl + << "what () const throw ()" + << "{" + << "bool ex (!expected_.empty ());" + << "bool en (!encountered_.empty ());" + << endl + << "return en" << endl + << " ? ex ? \"wrong group separator\" : \"unexpected group separator\"" << endl + << " : ex ? \"expected group separator\" : \"\";" + << "}"; + } + + // scanner + // + os << "// scanner" << endl + << "//" << endl + << "scanner::" << endl + << "~scanner ()" + << "{" + << "}"; + + // argv_scanner + // + os << "// argv_scanner" << endl + << "//" << endl + + << "bool argv_scanner::" << endl + << "more ()" + << "{" + << "return i_ < argc_;" + << "}" + + << "const char* argv_scanner::" << endl + << "peek ()" + << "{" + << "if (i_ < argc_)" << endl + << "return argv_[i_];" + << "else" << endl + << "throw eos_reached ();" + << "}" + + << "const char* argv_scanner::" << endl + << "next ()" + << "{" + << "if (i_ < argc_)" + << "{" + << "const char* r (argv_[i_]);" + << endl + << "if (erase_)" + << "{" + << "for (int i (i_ + 1); i < argc_; ++i)" << endl + << "argv_[i - 1] = argv_[i];" + << endl + << "--argc_;" + << "argv_[argc_] = 0;" + << "}" + << "else" << endl + << "++i_;" + << endl + << "return r;" + << "}" + << "else" << endl + << "throw eos_reached ();" + << "}" + + << "void argv_scanner::" << endl + << "skip ()" + << "{" + << "if (i_ < argc_)" << endl + << "++i_;" + << "else" << endl + << "throw eos_reached ();" + << "}"; + + // vector_scanner + // + if (ctx.options.generate_vector_scanner ()) + { + os << "// vector_scanner" << endl + << "//" << endl + + << "bool vector_scanner::" << endl + << "more ()" + << "{" + << "return i_ < v_.size ();" + << "}" + + << "const char* vector_scanner::" << endl + << "peek ()" + << "{" + << "if (i_ < v_.size ())" << endl + << "return v_[i_].c_str ();" + << "else" << endl + << "throw eos_reached ();" + << "}" + + << "const char* vector_scanner::" << endl + << "next ()" + << "{" + << "if (i_ < v_.size ())" << endl + << "return v_[i_++].c_str ();" + << "else" << endl + << "throw eos_reached ();" + << "}" + + << "void vector_scanner::" << endl + << "skip ()" + << "{" + << "if (i_ < v_.size ())" << endl + << "++i_;" + << "else" << endl + << "throw eos_reached ();" + << "}"; + } + + // argv_file_scanner + // + if (ctx.options.generate_file_scanner ()) + { + bool sep (!ctx.opt_sep.empty ()); + + const string& pfx (ctx.opt_prefix); + size_t pfx_n (pfx.size ()); + bool comb_values (pfx_n != 0 && !ctx.options.no_combined_values ()); + + os << "// argv_file_scanner" << endl + << "//" << endl + + << "int argv_file_scanner::zero_argc_ = 0;" + << "std::string argv_file_scanner::empty_string_;" + << endl + + << "bool argv_file_scanner::" << endl + << "more ()" + << "{" + << "if (!args_.empty ())" << endl + << "return true;" + << endl + << "while (base::more ())" + << "{" + << "// See if the next argument is the file option." << endl + << "//" << endl + << "const char* a (base::peek ());" + << "const option_info* oi = 0;" + << "const char* ov = 0;" + << endl; + + if (sep) + os << "if (!skip_)" + << "{"; + + os << "if ((oi = find (a)) != 0)" + << "{" + << "base::next ();" + << endl + << "if (!base::more ())" << endl + << "throw missing_value (a);" + << endl + << "ov = base::next ();" + << "}"; + + // Handle the combined option/value (--foo=bar). See the option parsing + // implementation for details. + // + if (comb_values) + { + os << "else if (std::strncmp (a, \"" << pfx << "\", " << + pfx_n << ") == 0)" // It looks like an option. + << "{" + << "if ((ov = std::strchr (a, '=')) != 0)" // Has '='. + << "{" + << "std::string o (a, 0, ov - a);" + << "if ((oi = find (o.c_str ())) != 0)" + << "{" + << "base::next ();" + << "++ov;" // That's the value. + << "}" + << "}" + << "}"; + } + + if (sep) + os << "}"; + + os << "if (oi != 0)" + << "{" + << "if (oi->search_func != 0)" + << "{" + << "std::string f (oi->search_func (ov, oi->arg));" + << endl + << "if (!f.empty ())" << endl + << "load (f);" + << "}" + << "else" << endl + << "load (ov);" + << endl + << "if (!args_.empty ())" << endl + << "return true;" + << "}" + << "else" + << "{"; + if (sep) + os << "if (!skip_)" << endl + << "skip_ = (std::strcmp (a, \"" << ctx.opt_sep << "\") == 0);" + << endl; + os << "return true;" + << "}" + << "}" // while + << "return false;" + << "}" + + << "const char* argv_file_scanner::" << endl + << "peek ()" + << "{" + << "if (!more ())" << endl + << "throw eos_reached ();" + << endl + << "return args_.empty () ? base::peek () : args_.front ().value.c_str ();" + << "}" + + << "const std::string& argv_file_scanner::" << endl + << "peek_file ()" + << "{" + << "if (!more ())" << endl + << "throw eos_reached ();" + << endl + << "return args_.empty () ? empty_string_ : *args_.front ().file;" + << "}" + + << "std::size_t argv_file_scanner::" << endl + << "peek_line ()" + << "{" + << "if (!more ())" << endl + << "throw eos_reached ();" + << endl + << "return args_.empty () ? 0 : args_.front ().line;" + << "}" + + << "const char* argv_file_scanner::" << endl + << "next ()" + << "{" + << "if (!more ())" << endl + << "throw eos_reached ();" + << endl + << "if (args_.empty ())" << endl + << "return base::next ();" + << "else" + << "{" + << "hold_[i_ == 0 ? ++i_ : --i_].swap (args_.front ().value);" + << "args_.pop_front ();" + << "return hold_[i_].c_str ();" + << "}" + << "}" + + << "void argv_file_scanner::" << endl + << "skip ()" + << "{" + << "if (!more ())" << endl + << "throw eos_reached ();" + << endl + << "if (args_.empty ())" << endl + << "return base::skip ();" + << "else" << endl + << "args_.pop_front ();" + << "}" + + << "const argv_file_scanner::option_info* argv_file_scanner::" << endl + << "find (const char* a) const" + << "{" + << "for (std::size_t i (0); i < options_count_; ++i)" << endl + << "if (std::strcmp (a, options_[i].option) == 0)" << endl + << "return &options_[i];" + << endl + << "return 0;" + << "}" + + << "void argv_file_scanner::" << endl + << "load (const std::string& file)" + << "{" + << "using namespace std;" + << endl + << "ifstream is (file.c_str ());" + << endl + << "if (!is.is_open ())" << endl + << "throw file_io_failure (file);" + << endl + << "files_.push_back (file);" + << endl + << "arg a;" + << "a.file = &*files_.rbegin ();" + << endl + << "for (a.line = 1; !is.eof (); ++a.line)" + << "{" + << "string line;" + << "getline (is, line);" + << endl + << "if (is.fail () && !is.eof ())" << endl + << "throw file_io_failure (file);" + << endl + << "string::size_type n (line.size ());" + << endl + << "// Trim the line from leading and trailing whitespaces." << endl + << "//" << endl + << "if (n != 0)" + << "{" + << "const char* f (line.c_str ());" + << "const char* l (f + n);" + << endl + << "const char* of (f);" + << "while (f < l && (*f == ' ' || *f == '\\t' || *f == '\\r'))" << endl + << "++f;" + << endl + << "--l;" + << endl + << "const char* ol (l);" + << "while (l > f && (*l == ' ' || *l == '\\t' || *l == '\\r'))" << endl + << "--l;" + << endl + << "if (f != of || l != ol)" << endl + << "line = f <= l ? string (f, l - f + 1) : string ();" + << "}" + << "// Ignore empty lines, those that start with #." << endl + << "//" << endl + << "if (line.empty () || line[0] == '#')" << endl + << "continue;" + << endl + << "string::size_type p (string::npos);"; + + // If we have the option prefix, then only consider lines that start + // with that as options. + // + if (pfx_n != 0) + os << "if (line.compare (0, " << pfx_n << ", \"" << pfx << "\") == 0)" + << "{"; + + os << "p = line.find (' ');"; + + // Handle the combined option/value (--foo=bar). This is a bit tricky + // since the equal sign can be part of the value (--foo bar=baz). + // + // Note that we cannot just pass it along as combined because of + // quoting (--foo="'bar baz'"). + // + if (comb_values) + { + os << endl + << "string::size_type q (line.find ('='));" + << "if (q != string::npos && q < p)" << endl + << "p = q;"; + } + + if (pfx_n != 0) + os << "}"; + else + os << endl; + + os << "string s1;" + << "if (p != string::npos)" + << "{" + << "s1.assign (line, 0, p);" + << endl + << "// Skip leading whitespaces in the argument." << endl + << "//" << endl; + if (comb_values) + os << "if (line[p] == '=')" << endl // Keep whitespaces after '='. + << "++p;" + << "else" + << "{"; + os << "n = line.size ();" + << "for (++p; p < n; ++p)" + << "{" + << "char c (line[p]);" + << "if (c != ' ' && c != '\\t' && c != '\\r')" << endl + << "break;" + << "}"; + if (comb_values) + os << "}"; + os << "}"; + if (sep) + os << "else if (!skip_)" << endl + << "skip_ = (line == \"" << ctx.opt_sep << "\");" + << endl; + + os << "string s2 (line, p != string::npos ? p : 0);" + << endl + << "// If the string (which is an option value or argument) is" << endl + << "// wrapped in quotes, remove them." << endl + << "//" << endl + << "n = s2.size ();" + << "char cf (s2[0]), cl (s2[n - 1]);" + << endl + << "if (cf == '\"' || cf == '\\'' || cl == '\"' || cl == '\\'')" + << "{" + << "if (n == 1 || cf != cl)" << endl + << "throw unmatched_quote (s2);" + << endl + << "s2 = string (s2, 1, n - 2);" + << "}"; + + os << "if (!s1.empty ())" + << "{" + << "// See if this is another file option." << endl + << "//" << endl + << "const option_info* oi;" + << "if (" << (sep ? "!skip_ && " : "") << + "(oi = find (s1.c_str ())))" << endl + << "{" + << "if (s2.empty ())" << endl + << "throw missing_value (oi->option);" + << endl + << "if (oi->search_func != 0)" + << "{" + << "std::string f (oi->search_func (s2.c_str (), oi->arg));" + << "if (!f.empty ())" << endl + << "load (f);" + << "}" + << "else" << endl + << "load (s2);" + << endl + << "continue;" + << "}" + << "a.value = s1;" + << "args_.push_back (a);" + << "}" + << "a.value = s2;" + << "args_.push_back (a);" + << "}" // while + << "}"; + } + + // group_scanner + // + if (ctx.options.generate_group_scanner ()) + { + os << "// group_scanner" << endl + << "//" << endl + + << "bool group_scanner::" << endl + << "more ()" + << "{" + << "// We don't want to call scan_group() here since that" << endl + << "// would invalidate references to previous arguments." << endl + << "// But we do need to check that the previous group was" << endl + << "// handled." << endl + << "//" << endl + << "if (state_ == scanned)" + << "{" + << "if (group_scan_.end () != group_.size ())" << endl + << "throw unexpected_group (arg_[i_], group_scan_.next ());" + << "}" + << "return scan_.more ();" + << "}" + + << "const char* group_scanner::" << endl + << "peek ()" + << "{" + << "if (state_ != peeked)" << endl + << "scan_group (peeked);" + << "scan_.peek ();" + << "// Return unescaped." << endl + << "return arg_[i_].c_str ();" + << "}" + + << "const char* group_scanner::" << endl + << "next ()" + << "{" + << "if (state_ != peeked)" << endl + << "scan_group (peeked);" + << "scan_.next ();" + << "scan_group (scanned);" + << "// Return unescaped." << endl + << "return arg_[i_].c_str ();" + << "}" + + << "void group_scanner::" << endl + << "skip ()" + << "{" + << "if (state_ != peeked)" << endl + << "scan_group (peeked);" + << "scan_.skip ();" + << "scan_group (skipped);" + << "}" + + << "void group_scanner::" << endl + << "scan_group (state st)" + << "{" + << "// If the previous argument has been scanned, then make" << endl + << "// sure the group has been scanned (handled) as well." << endl + << "//" << endl + << "if (state_ == scanned)" + << "{" + << "if (group_scan_.end () != group_.size ())" << endl + << "throw unexpected_group (arg_[i_], group_scan_.next ());" + << "}" + + << "if (state_ != peeked)" + << "{" + << "arg_[i_ == 0 ? ++i_ : --i_].clear ();" + << "group_.clear ();" + << "group_scan_.reset ();" + << "}" + + << "// We recognize all group sequences both before and " << endl + << "// after the argument and diagnose any misuse. We may" << endl + << "// also have multiple groups:" << endl + << "//" << endl + << "// { -x }+ { -y }+ arg" << endl + << "//" << endl + << endl + << "// Using group_ won't cover empty groups." << endl + << "//" << endl + << "bool g (false);" + << endl + << "while (scan_.more ())" + << "{" + << "const char* a (scan_.peek ());" + << "size_t i (*a == '\\\\' ? 1 : 0);" + << "separator s (sense (a + i));" + << endl + << "if (s == none || i != 0)" + << "{" + << "if (state_ != peeked)" << endl + << "arg_[i_] = a + (s != none ? i : 0);" + << "break;" + << "}" + + << "// Start of a leading group for the next argument." << endl + << "//" << endl + << "if (s == open && state_ == peeked)" << endl + << "break;" + << endl + << "if (s != (state_ == peeked ? open_plus : open))" << endl + << "throw group_separator (a, \"\");" + << endl + << "g = true;" + << endl + << "// Scan the group until the closing separator." << endl + << "//" << endl + << "scan_.next ();" + << "s = none;" + << "while (s == none && scan_.more ())" + << "{" + << "a = scan_.next ();" + << "i = (*a == '\\\\' ? 1 : 0);" + << "s = sense (a + i);" + << endl + << "if (s == none || i != 0)" + << "{" + << "group_.push_back (a + (s != none ? i : 0));" + << "s = none;" + << "}" + << "}" + + << "if (s != (state_ == peeked ? close : close_plus))" + << "{" + << "throw group_separator ((s != none ? a : \"\")," << endl + << "(state_ == peeked ? \"}\" : \"}+\"));" + << "}" + << "}" + + << "// Handle the case where we have seen the leading group" << endl + << "// but there are no more arguments." << endl + << "//" << endl + << "if (g && state_ != peeked && !scan_.more ())" << endl + << "throw group_separator (\"{\", \"\");" + << endl + << "state_ = st;" + << "}"; + } + + // Option description. + // + if (ctx.options.generate_description ()) + { + // options + // + os << "void options::" << endl + << "push_back (const option& o)" + << "{" + << "container_type::size_type n (size ());" + << "container_type::push_back (o);" + << "map_[o.name ()] = n;" + << endl + << "for (option_names::const_iterator i (o.aliases ().begin ());" << endl + << "i != o.aliases ().end (); ++i)" << endl + << "map_[*i] = n;" + << "}"; + } + } + + // To reduce the number of standard headers we have to include in the + // generated header file, we always generate the following templates + // in the source file. + // + bool sp (ctx.gen_specifier); + bool gen_merge (ctx.gen_merge); + + // parser class template & its specializations + // + os << "template <typename X>" << endl + << "struct parser" + << "{"; + + os << "static void" << endl + << "parse (X& x, " << (sp ? "bool& xs, " : "") << "scanner& s)" + << "{" + << "using namespace std;" + << endl + << "const char* o (s.next ());" + << "if (s.more ())" + << "{" + << "string v (s.next ());" + << "istringstream is (v);" + << "if (!(is >> x && is.peek () == istringstream::traits_type::eof ()))" << endl + << "throw invalid_value (o, v);" + << "}" + << "else" << endl + << "throw missing_value (o);"; + if (sp) + os << endl + << "xs = true;"; + os << "}"; + + if (gen_merge) + os << "static void" << endl + << "merge (X& b, const X& a)" + << "{" + << "b = a;" + << "}"; + + os << "};"; + + // parser<bool> + // + os << "template <>" << endl + << "struct parser<bool>" + << "{"; + + os << "static void" << endl + << "parse (bool& x, scanner& s)" + << "{" + << "s.next ();" + << "x = true;" + << "}"; + + if (gen_merge) + os << "static void" << endl + << "merge (bool& b, const bool&)" + << "{" + << "b = true;" // We wouldn't be here if a is false. + << "}"; + + os << "};"; + + // parser<string> + // + os << "template <>" << endl + << "struct parser<std::string>" + << "{"; + + os << "static void" << endl + << "parse (std::string& x, " << (sp ? "bool& xs, " : "") << "scanner& s)" + << "{" + << "const char* o (s.next ());" + << endl + << "if (s.more ())" << endl + << "x = s.next ();" + << "else" << endl + << "throw missing_value (o);"; + if (sp) + os << endl + << "xs = true;"; + os << "}"; + + if (gen_merge) + os << "static void" << endl + << "merge (std::string& b, const std::string& a)" + << "{" + << "b = a;" + << "}"; + + os << "};"; + + // parser<std::vector<X>> + // + os << "template <typename X>" << endl + << "struct parser<std::vector<X> >" + << "{"; + + os << "static void" << endl + << "parse (std::vector<X>& c, " << (sp ? "bool& xs, " : "") << "scanner& s)" + << "{" + << "X x;"; + if (sp) + os << "bool dummy;"; + os << "parser<X>::parse (x, " << (sp ? "dummy, " : "") << "s);" + << "c.push_back (x);"; + if (sp) + os << "xs = true;"; + os << "}"; + + if (gen_merge) + os << "static void" << endl + << "merge (std::vector<X>& b, const std::vector<X>& a)" + << "{" + << "b.insert (b.end (), a.begin (), a.end ());" + << "}"; + + os << "};"; + + // parser<std::set<X>> + // + os << "template <typename X>" << endl + << "struct parser<std::set<X> >" + << "{"; + + os << "static void" << endl + << "parse (std::set<X>& c, " << (sp ? "bool& xs, " : "") << "scanner& s)" + << "{" + << "X x;"; + if (sp) + os << "bool dummy;"; + os << "parser<X>::parse (x, " << (sp ? "dummy, " : "") << "s);" + << "c.insert (x);"; + if (sp) + os << "xs = true;"; + os << "}"; + + if (gen_merge) + os << "static void" << endl + << "merge (std::set<X>& b, const std::set<X>& a)" + << "{" + << "b.insert (a.begin (), a.end ());" + << "}"; + + os << "};"; + + // parser<std::map<K,V>> + // + os << "template <typename K, typename V>" << endl + << "struct parser<std::map<K, V> >" + << "{"; + + os << "static void" << endl + << "parse (std::map<K, V>& m, " << (sp ? "bool& xs, " : "") << "scanner& s)" + << "{" + << "const char* o (s.next ());" + << endl + << "if (s.more ())" + << "{" + << "std::string ov (s.next ());" + << "std::string::size_type p = ov.find ('=');" + << endl + << "K k = K ();" + << "V v = V ();" + << "std::string kstr (ov, 0, p);" + << "std::string vstr (ov, (p != std::string::npos ? p + 1 : ov.size ()));" + << endl + << "int ac (2);" + << "char* av[] = {const_cast<char*> (o), 0};"; + if (sp) + os << "bool dummy;"; + os << "if (!kstr.empty ())" + << "{" + << "av[1] = const_cast<char*> (kstr.c_str ());" + << "argv_scanner s (0, ac, av);" + << "parser<K>::parse (k, " << (sp ? "dummy, " : "") << "s);" + << "}" + << "if (!vstr.empty ())" + << "{" + << "av[1] = const_cast<char*> (vstr.c_str ());" + << "argv_scanner s (0, ac, av);" + << "parser<V>::parse (v, " << (sp ? "dummy, " : "") << "s);" + << "}" + << "m[k] = v;" + << "}" + << "else" << endl + << "throw missing_value (o);"; + if (sp) + os << endl + << "xs = true;"; + os << "}"; + + if (gen_merge) + os << "static void" << endl + << "merge (std::map<K, V>& b, const std::map<K, V>& a)" + << "{" + << "for (typename std::map<K, V>::const_iterator i (a.begin ()); " << endl + << "i != a.end (); " << endl + << "++i)" << endl + << "b[i->first] = i->second;" + << "}"; + + os << "};"; + + // Parser thunk. + // + os << "template <typename X, typename T, T X::*M>" << endl + << "void" << endl + << "thunk (X& x, scanner& s)" + << "{" + << "parser<T>::parse (x.*M, s);" + << "}"; + + if (ctx.gen_specifier) + os << "template <typename X, typename T, T X::*M, bool X::*S>" << endl + << "void" << endl + << "thunk (X& x, scanner& s)" + << "{" + << "parser<T>::parse (x.*M, x.*S, s);" + << "}"; + + ctx.ns_close (ctx.cli); +} diff --git a/cli/cli/runtime-source.hxx b/cli/cli/runtime-source.hxx new file mode 100644 index 0000000..0e51c4b --- /dev/null +++ b/cli/cli/runtime-source.hxx @@ -0,0 +1,13 @@ +// file : cli/runtime-source.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_RUNTIME_SOURCE_HXX +#define CLI_RUNTIME_SOURCE_HXX + +#include <cli/context.hxx> + +void +generate_runtime_source (context&, bool complete); + +#endif // CLI_RUNTIME_SOURCE_HXX diff --git a/cli/cli/semantics.hxx b/cli/cli/semantics.hxx new file mode 100644 index 0000000..4b18629 --- /dev/null +++ b/cli/cli/semantics.hxx @@ -0,0 +1,16 @@ +// file : cli/semantics.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_SEMANTICS_HXX +#define CLI_SEMANTICS_HXX + +#include <cli/semantics/class.hxx> +#include <cli/semantics/doc.hxx> +#include <cli/semantics/elements.hxx> +#include <cli/semantics/expression.hxx> +#include <cli/semantics/namespace.hxx> +#include <cli/semantics/option.hxx> +#include <cli/semantics/unit.hxx> + +#endif // CLI_SEMANTICS_HXX diff --git a/cli/cli/semantics/class.cxx b/cli/cli/semantics/class.cxx new file mode 100644 index 0000000..494d5d0 --- /dev/null +++ b/cli/cli/semantics/class.cxx @@ -0,0 +1,39 @@ +// file : cli/semantics/class.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <cutl/compiler/type-info.hxx> + +#include <cli/semantics/class.hxx> + +namespace semantics +{ + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + // inherits + // + { + type_info ti (typeid (inherits)); + ti.add_base (typeid (edge)); + insert (ti); + } + + // class_ + // + { + type_info ti (typeid (class_)); + ti.add_base (typeid (scope)); + insert (ti); + } + } + } init_; + } +} diff --git a/cli/cli/semantics/class.hxx b/cli/cli/semantics/class.hxx new file mode 100644 index 0000000..ca7de8c --- /dev/null +++ b/cli/cli/semantics/class.hxx @@ -0,0 +1,106 @@ +// file : cli/semantics/class.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_SEMANTICS_CLASS_HXX +#define CLI_SEMANTICS_CLASS_HXX + +#include <vector> + +#include <cli/semantics/elements.hxx> + +namespace semantics +{ + class class_; + + class inherits: public edge + { + public: + class_& + base () const + { + return *base_; + } + + class_& + derived () const + { + return *derived_; + } + + public: + void + set_left_node (class_& n) + { + derived_ = &n; + } + + void + set_right_node (class_& n) + { + base_ = &n; + } + + protected: + class_* base_; + class_* derived_; + }; + + class class_: public scope + { + private: + typedef std::vector<inherits*> inherits_list; + + public: + bool + abstract () const + { + return abstract_; + } + + void + abstract (bool a) + { + abstract_ = a; + } + + public: + typedef pointer_iterator<inherits_list::const_iterator> inherits_iterator; + + inherits_iterator + inherits_begin () const + { + return inherits_.begin (); + } + + inherits_iterator + inherits_end () const + { + return inherits_.end (); + } + + public: + class_ (path const& file, size_t line, size_t column) + : node (file, line, column), abstract_ (false) + { + } + + void + add_edge_left (inherits& e) + { + inherits_.push_back (&e); + } + + void + add_edge_right (inherits&) {} + + using scope::add_edge_left; + using scope::add_edge_right; + + private: + bool abstract_; + inherits_list inherits_; + }; +} + +#endif // CLI_SEMANTICS_CLASS_HXX diff --git a/cli/cli/semantics/doc.cxx b/cli/cli/semantics/doc.cxx new file mode 100644 index 0000000..c31260c --- /dev/null +++ b/cli/cli/semantics/doc.cxx @@ -0,0 +1,27 @@ +// file : cli/semantics/doc.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <cutl/compiler/type-info.hxx> + +#include <cli/semantics/doc.hxx> + +namespace semantics +{ + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + type_info ti (typeid (doc)); + ti.add_base (typeid (nameable)); + insert (ti); + } + } init_; + } +} diff --git a/cli/cli/semantics/doc.hxx b/cli/cli/semantics/doc.hxx new file mode 100644 index 0000000..8dacb7a --- /dev/null +++ b/cli/cli/semantics/doc.hxx @@ -0,0 +1,22 @@ +// file : cli/semantics/doc.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_SEMANTICS_DOC_HXX +#define CLI_SEMANTICS_DOC_HXX + +#include <cli/semantics/elements.hxx> + +namespace semantics +{ + // Scope-level documentation node. + // + class doc: public nameable, public doc_strings + { + public: + doc (path const& file, size_t line, size_t column) + : node (file, line, column) {} + }; +} + +#endif // CLI_SEMANTICS_DOC_HXX diff --git a/cli/cli/semantics/elements.cxx b/cli/cli/semantics/elements.cxx new file mode 100644 index 0000000..ed8eb7d --- /dev/null +++ b/cli/cli/semantics/elements.cxx @@ -0,0 +1,129 @@ +// file : cli/semantics/elements.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <cutl/compiler/type-info.hxx> + +#include <cli/semantics/elements.hxx> + +namespace semantics +{ + // nameable + // + string nameable:: + fq_name () const + { + string const& n (name ()); + + if (n.empty ()) + return n; + else + return scope ().fq_name () + "::" + n; + } + + // scope + // + + scope::names_iterator_pair scope:: + find (string const& name) const + { + names_map::const_iterator i (names_map_.find (name)); + + if (i == names_map_.end ()) + return names_iterator_pair (names_.end (), names_.end ()); + else + return names_iterator_pair (i->second.begin (), i->second.end ()); + } + + scope::names_iterator scope:: + find (names& e) + { + list_iterator_map::iterator i (iterator_map_.find (&e)); + return i != iterator_map_.end () ? i->second : names_.end (); + } + + void scope:: + add_edge_left (names& e) + { + names_list::iterator it (names_.insert (names_.end (), &e)); + iterator_map_[&e] = it; + + for (names::name_iterator i (e.name_begin ()); i != e.name_end (); ++i) + names_map_[*i].push_back (&e); + } + + void scope:: + remove_edge_left (names& e) + { + list_iterator_map::iterator i (iterator_map_.find (&e)); + assert (i != iterator_map_.end ()); + + names_.erase (i->second); + iterator_map_.erase (i); + + for (names::name_iterator ni (e.name_begin ()); ni != e.name_end (); ++ni) + { + names_map::iterator j (names_map_.find (*ni)); + + for (names_list::iterator i (j->second.begin ()); + i != j->second.end (); ++i) + { + if (*i == &e) + i = j->second.erase (i); + } + } + } + + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + // node + // + insert (type_info (typeid (node))); + + // edge + // + insert (type_info (typeid (edge))); + + // names + // + { + type_info ti (typeid (names)); + ti.add_base (typeid (edge)); + insert (ti); + } + + // nameable + // + { + type_info ti (typeid (nameable)); + ti.add_base (typeid (node)); + insert (ti); + } + + // scope + // + { + type_info ti (typeid (scope)); + ti.add_base (typeid (nameable)); + insert (ti); + } + + // type + // + { + type_info ti (typeid (type)); + ti.add_base (typeid (node)); + insert (ti); + } + } + } init_; + } +} diff --git a/cli/cli/semantics/elements.hxx b/cli/cli/semantics/elements.hxx new file mode 100644 index 0000000..6235a06 --- /dev/null +++ b/cli/cli/semantics/elements.hxx @@ -0,0 +1,407 @@ +// file : cli/semantics/elements.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_SEMANTICS_ELEMENTS_HXX +#define CLI_SEMANTICS_ELEMENTS_HXX + +#include <map> +#include <list> +#include <vector> +#include <string> +#include <cstddef> // std::size_t +#include <cstdlib> // std::abort +#include <utility> // std::pair +#include <cassert> + +#include <cutl/fs/path.hxx> + +#include <cutl/container/graph.hxx> +#include <cutl/container/pointer-iterator.hxx> + +#include <cutl/compiler/context.hxx> + +namespace semantics +{ + using namespace cutl; + + using std::size_t; + using std::string; + + using container::graph; + using container::pointer_iterator; + + using compiler::context; + + + // + // + using fs::path; + using fs::invalid_path; + + // + // + typedef std::vector<string> doc_strings; + + // + // + class node; + class edge; + + + // + // + class edge + { + public: + virtual + ~edge () {} + + public: + typedef semantics::context context_type; + + context_type& + context () + { + return context_; + } + + public: + template <typename X> + bool + is_a () const + { + return dynamic_cast<X const*> (this) != 0; + } + + private: + context_type context_; + }; + + // + // + class node + { + public: + virtual + ~node () {} + + public: + typedef semantics::context context_type; + + context_type& + context () + { + return context_; + } + + public: + path const& + file () const + { + return file_; + } + + size_t + line () const + { + return line_; + } + + size_t + column () const + { + return column_; + } + + public: + template <typename X> + bool + is_a () const + { + return dynamic_cast<X const*> (this) != 0; + } + + public: + node (path const& file, size_t line, size_t column) + : file_ (file), line_ (line), column_ (column) + { + } + + // For virtual inheritance. Should never be actually called. + // + node () + : file_ ("") + { + std::abort (); + } + + // Sink functions that allow extensions in the form of one-way + // edges. + // + void + add_edge_right (edge&) + { + } + + private: + context_type context_; + path file_; + size_t line_; + size_t column_; + }; + + // + // + class scope; + class nameable; + + + // + // + class names: public edge + { + public: + typedef semantics::scope scope_type; + + typedef std::vector<string> name_list; + typedef name_list::const_iterator name_iterator; + + // First name. + // + string const& + name () const + { + return names_[0]; + } + + name_iterator + name_begin () const + { + return names_.begin (); + } + + name_iterator + name_end () const + { + return names_.end (); + } + + scope_type& + scope () const + { + return *scope_; + } + + nameable& + named () const + { + return *named_; + } + + public: + names (string const& name) + { + names_.push_back (name); + } + + names (name_list const& names) + : names_ (names) + { + } + + void + set_left_node (scope_type& n) + { + scope_ = &n; + } + + void + set_right_node (nameable& n) + { + named_ = &n; + } + + void + clear_left_node (scope_type& n) + { + assert (scope_ == &n); + scope_ = 0; + } + + void + clear_right_node (nameable& n) + { + assert (named_ == &n); + named_ = 0; + } + + protected: + scope_type* scope_; + nameable* named_; + name_list names_; + }; + + + // + // + class nameable: public virtual node + { + public: + typedef semantics::scope scope_type; + + string + name () const + { + return named_->name (); + } + + string + fq_name () const; + + scope_type& + scope () + { + return named_->scope (); + } + + scope_type const& + scope () const + { + return named_->scope (); + } + + names& + named () + { + return *named_; + } + + public: + nameable () + : named_ (0) + { + } + + void + add_edge_right (names& e) + { + assert (named_ == 0); + named_ = &e; + } + + void + remove_edge_right (names& e) + { + assert (named_ == &e); + named_ = 0; + } + + using node::add_edge_right; + + private: + names* named_; + }; + + + // + // + class scope: public virtual nameable + { + protected: + typedef std::list<names*> names_list; + typedef std::map<names*, names_list::iterator> list_iterator_map; + typedef std::map<string, names_list> names_map; + + public: + typedef pointer_iterator<names_list::iterator> names_iterator; + typedef pointer_iterator<names_list::const_iterator> names_const_iterator; + + typedef + std::pair<names_const_iterator, names_const_iterator> + names_iterator_pair; + + public: + names_iterator + names_begin () + { + return names_.begin (); + } + + names_iterator + names_end () + { + return names_.end (); + } + + names_const_iterator + names_begin () const + { + return names_.begin (); + } + + names_const_iterator + names_end () const + { + return names_.end (); + } + + bool + names_empty () const + { + return names_.empty (); + } + + virtual names_iterator_pair + find (string const& name) const; + + names_iterator + find (names&); + + public: + scope (path const& file, size_t line, size_t column) + : node (file, line, column) + { + } + + scope () + { + } + + void + add_edge_left (names&); + + void + remove_edge_left (names&); + + using nameable::add_edge_right; + + private: + names_list names_; + list_iterator_map iterator_map_; + names_map names_map_; + }; + + // + // + class type: public node + { + public: + string const& + name () const + { + return name_; + } + + public: + type (path const& file, size_t line, size_t column, string const& name) + : node (file, line, column), name_ (name) + { + } + + private: + string name_; + }; +} + +#endif // CLI_SEMANTICS_ELEMENTS_HXX diff --git a/cli/cli/semantics/expression.cxx b/cli/cli/semantics/expression.cxx new file mode 100644 index 0000000..18d3312 --- /dev/null +++ b/cli/cli/semantics/expression.cxx @@ -0,0 +1,27 @@ +// file : cli/semantics/expression.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <cutl/compiler/type-info.hxx> + +#include <cli/semantics/expression.hxx> + +namespace semantics +{ + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + type_info ti (typeid (expression)); + ti.add_base (typeid (node)); + insert (ti); + } + } init_; + } +} diff --git a/cli/cli/semantics/expression.hxx b/cli/cli/semantics/expression.hxx new file mode 100644 index 0000000..e36a0cb --- /dev/null +++ b/cli/cli/semantics/expression.hxx @@ -0,0 +1,76 @@ +// file : cli/semantics/expression.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_SEMANTICS_EXPRESSION_HXX +#define CLI_SEMANTICS_EXPRESSION_HXX + +#include <string> +#include <cli/semantics/elements.hxx> + +namespace semantics +{ + // + // + class initialized; + + // + // + class expression: public node + { + public: + enum expression_type + { + string_lit, + char_lit, + bool_lit, + int_lit, + float_lit, + call_expr, + identifier + }; + + expression_type + type () const + { + return type_; + } + + std::string const& + value () const + { + return value_; + } + + public: + initialized& + initializes () const + { + return *initializes_; + } + + public: + expression (path const& file, + size_t line, + size_t column, + expression_type type, + std::string const& value) + : node (file, line, column), type_ (type), value_ (value) + { + } + + void + add_edge_right (initialized& e) + { + initializes_ = &e; + } + + private: + initialized* initializes_; + + expression_type type_; + std::string value_; + }; +} + +#endif // CLI_SEMANTICS_EXPRESSION_HXX diff --git a/cli/cli/semantics/namespace.cxx b/cli/cli/semantics/namespace.cxx new file mode 100644 index 0000000..3c2643c --- /dev/null +++ b/cli/cli/semantics/namespace.cxx @@ -0,0 +1,27 @@ +// file : cli/semantics/namespace.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <cutl/compiler/type-info.hxx> + +#include <cli/semantics/namespace.hxx> + +namespace semantics +{ + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + type_info ti (typeid (namespace_)); + ti.add_base (typeid (scope)); + insert (ti); + } + } init_; + } +} diff --git a/cli/cli/semantics/namespace.hxx b/cli/cli/semantics/namespace.hxx new file mode 100644 index 0000000..00c7bfc --- /dev/null +++ b/cli/cli/semantics/namespace.hxx @@ -0,0 +1,26 @@ +// file : cli/semantics/namespace.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_SEMANTICS_NAMESPACE_HXX +#define CLI_SEMANTICS_NAMESPACE_HXX + +#include <cli/semantics/elements.hxx> + +namespace semantics +{ + class namespace_: public scope + { + public: + namespace_ (path const& file, size_t line, size_t column) + : node (file, line, column) + { + } + + namespace_ () + { + } + }; +} + +#endif // CLI_SEMANTICS_NAMESPACE_HXX diff --git a/cli/cli/semantics/option.cxx b/cli/cli/semantics/option.cxx new file mode 100644 index 0000000..8746a5e --- /dev/null +++ b/cli/cli/semantics/option.cxx @@ -0,0 +1,47 @@ +// file : cli/semantics/option.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <cutl/compiler/type-info.hxx> + +#include <cli/semantics/option.hxx> + +namespace semantics +{ + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + // belongs + // + { + type_info ti (typeid (belongs)); + ti.add_base (typeid (edge)); + insert (ti); + } + + // initializes + // + { + type_info ti (typeid (initialized)); + ti.add_base (typeid (edge)); + insert (ti); + } + + // option + // + { + type_info ti (typeid (option)); + ti.add_base (typeid (nameable)); + insert (ti); + } + } + } init_; + } +} diff --git a/cli/cli/semantics/option.hxx b/cli/cli/semantics/option.hxx new file mode 100644 index 0000000..a9bb963 --- /dev/null +++ b/cli/cli/semantics/option.hxx @@ -0,0 +1,189 @@ +// file : cli/semantics/option.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_SEMANTICS_OPTION_HXX +#define CLI_SEMANTICS_OPTION_HXX + +#include <cli/semantics/elements.hxx> + +namespace semantics +{ + // + // + class option; + + // + // + class belongs: public edge + { + public: + typedef semantics::type type_type; + typedef semantics::option option_type; + + option_type& + option () const + { + return *option_; + } + + type_type& + type () const + { + return *type_; + } + + public: + void + set_left_node (option_type& n) + { + option_ = &n; + } + + void + set_right_node (type_type& n) + { + type_ = &n; + } + + private: + option_type* option_; + type_type* type_; + }; + + // + // + class expression; + + // + // + class initialized: public edge + { + public: + typedef semantics::option option_type; + typedef semantics::expression expression_type; + + option_type& + option () const + { + return *option_; + } + + expression_type& + expression () const + { + return *expression_; + } + + public: + void + set_left_node (option_type& n) + { + option_ = &n; + } + + void + set_right_node (expression_type& n) + { + expression_ = &n; + } + + private: + option_type* option_; + expression_type* expression_; + }; + + // + // + class option: public nameable + { + public: + typedef semantics::belongs belongs_type; + typedef semantics::type type_type; + + belongs_type& + belongs () const + { + return *belongs_; + } + + type_type& + type () const + { + return belongs_->type (); + } + + public: + typedef semantics::initialized initialized_type; + + bool + initialized_p () const + { + return initialized_ != 0; + } + + initialized_type& + initialized () const + { + return *initialized_; + } + + expression& + initializer () const + { + return initialized_->expression (); + } + + public: + typedef doc_strings::const_iterator doc_iterator; + + doc_iterator + doc_begin () const + { + return doc_.begin (); + } + + doc_iterator + doc_end () const + { + return doc_.end (); + } + + doc_strings const& + doc () const + { + return doc_; + } + + doc_strings& + doc () + { + return doc_; + } + + public: + option (path const& file, size_t line, size_t column) + : node (file, line, column), initialized_ (0) + { + } + + void + add_edge_left (belongs_type& e) + { + belongs_ = &e; + } + + void + add_edge_left (initialized_type& e) + { + initialized_ = &e; + } + + private: + belongs_type* belongs_; + initialized_type* initialized_; + doc_strings doc_; + }; +} + +#endif // CLI_SEMANTICS_OPTION_HXX diff --git a/cli/cli/semantics/unit.cxx b/cli/cli/semantics/unit.cxx new file mode 100644 index 0000000..9c532ea --- /dev/null +++ b/cli/cli/semantics/unit.cxx @@ -0,0 +1,63 @@ +// file : cli/semantics/unit.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <cutl/compiler/type-info.hxx> + +#include <cli/semantics/unit.hxx> + +namespace semantics +{ + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + // includes + // + { + type_info ti (typeid (includes)); + ti.add_base (typeid (edge)); + insert (ti); + } + + // cxx_includes + // + { + type_info ti (typeid (cxx_includes)); + ti.add_base (typeid (includes)); + insert (ti); + } + + // cli_includes + // + { + type_info ti (typeid (cli_includes)); + ti.add_base (typeid (includes)); + insert (ti); + } + + // cxx_unit + // + { + type_info ti (typeid (cxx_unit)); + ti.add_base (typeid (node)); + insert (ti); + } + + // cli_unit + // + { + type_info ti (typeid (cli_unit)); + ti.add_base (typeid (namespace_)); + insert (ti); + } + } + } init_; + } +} diff --git a/cli/cli/semantics/unit.hxx b/cli/cli/semantics/unit.hxx new file mode 100644 index 0000000..e37648a --- /dev/null +++ b/cli/cli/semantics/unit.hxx @@ -0,0 +1,310 @@ +// file : cli/semantics/unit.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_SEMANTICS_UNIT_HXX +#define CLI_SEMANTICS_UNIT_HXX + +#include <map> +#include <vector> +#include <string> + +#include <cli/semantics/elements.hxx> +#include <cli/semantics/namespace.hxx> + +namespace semantics +{ + // + // + class cli_unit; + class cxx_unit; + + // + // + class includes: public edge + { + public: + enum kind_type {quote, bracket}; + + cli_unit& + includer () const + { + return *includer_; + } + + kind_type + kind () const + { + return kind_; + } + + path const& + file () const + { + return file_; + } + + public: + includes (kind_type kind, path const& file) + : kind_ (kind), file_ (file) + { + } + + void + set_left_node (cli_unit& n) + { + includer_ = &n; + } + + protected: + kind_type kind_; + path file_; + cli_unit* includer_; + }; + + // + // + class cli_includes: public includes + { + public: + cli_unit& + includee () const + { + return *includee_; + } + + public: + cli_includes (kind_type kind, path const& file) + : includes (kind, file) + { + } + + void + set_right_node (cli_unit& n) + { + includee_ = &n; + } + + private: + cli_unit* includee_; + }; + + // + // + class cxx_includes: public includes + { + public: + cxx_unit& + includee () const + { + return *includee_; + } + + public: + cxx_includes (kind_type kind, path const& file) + : includes (kind, file) + { + } + + void + set_right_node (cxx_unit& n) + { + includee_ = &n; + } + + private: + cxx_unit* includee_; + }; + + // + // + class cxx_unit: public node + { + public: + cxx_unit (path const& file, size_t line, size_t column) + : node (file, line, column) + { + } + + void + add_edge_right (cxx_includes&) + { + } + }; + + // + // + class cli_unit: public graph<node, edge>, public namespace_ + { + typedef std::vector<includes*> includes_list; + + public: + // Lookup a name in the specified starting scope. Empty scope denotes + // the global namespace. Starting scope should be a fully-qualified + // name while name can be qualified but should not be fully-qualified + // (to lookup a fully-qualified name use the global namespace as the + // starting scope). + // + // The lookup starts in this unit and continues in all the units that + // this unit includes, transitively. + // + // The outer flag specifies whether to search the outer scopes. + // + template <typename T> + T* + lookup (std::string const& scope, + std::string const& name, + bool outer = true); + + public: + typedef + pointer_iterator<includes_list::const_iterator> + includes_iterator; + + includes_iterator + includes_begin () const + { + return includes_.begin (); + } + + includes_iterator + includes_end () const + { + return includes_.end (); + } + + public: + cli_unit (path const& file, size_t line, size_t column) + : node (file, line, column), graph_ (*this) + { + // Use a special edge to get this->name() return the global + // namespace name (""). + // + new_edge<global_names> (*this, *this); + } + + public: + template <typename T> + T& + new_node (path const& file, size_t line, size_t column) + { + return graph_.new_node<T> (file, line, column); + } + + template <typename T, typename A0> + T& + new_node (path const& file, size_t line, size_t column, A0 const& a0) + { + return graph_.new_node<T> (file, line, column, a0); + } + + template <typename T, typename A0, typename A1> + T& + new_node (path const& file, size_t line, size_t column, + A0 const& a0, A1 const& a1) + { + return graph_.new_node<T> (file, line, column, a0, a1); + } + + template <typename T, typename A0, typename A1, typename A2> + T& + new_node (path const& file, size_t line, size_t column, + A0 const& a0, A1 const& a1, A2 const& a2) + { + return graph_.new_node<T> (file, line, column, a0, a1, a2); + } + + template <typename T, typename A0, typename A1, typename A2, typename A3> + T& + new_node (path const& file, size_t line, size_t column, + A0 const& a0, A1 const& a1, A2 const& a2, A3 const& a3) + { + return graph_.new_node<T> (file, line, column, a0, a1, a2, a3); + } + + public: + type& + new_type (path const& file, size_t l, size_t c, string const& name) + { + type_map::iterator i (types_.find (name)); + + return i != types_.end () + ? *i->second + : *(types_[name] = &new_node<type> (file, l, c, name)); + } + + protected: + // Special names edge for the global namespace. + // + class global_names: public names + { + public: + global_names () + : names ("") + { + scope_ = 0; + } + + void + set_left_node (cli_unit&) + { + } + + void + set_right_node (nameable& n) + { + named_ = &n; + } + + void + clear_left_node (cli_unit&) + { + } + + void + clear_right_node (nameable& n) + { + assert (named_ == &n); + named_ = 0; + } + }; + + public: + void + add_edge_left (cli_includes& e) + { + includes_.push_back (&e); + } + + void + add_edge_left (cxx_includes& e) + { + includes_.push_back (&e); + } + + void + add_edge_left (global_names&) + { + } + + void + add_edge_right (cli_includes&) + { + } + + using namespace_::add_edge_left; + using namespace_::add_edge_right; + + private: + typedef std::map<string, type*> type_map; + + private: + graph<node, edge>& graph_; + includes_list includes_; + type_map types_; + }; +} + +#include <cli/semantics/unit.txx> + +#endif // CLI_SEMANTICS_UNIT_HXX diff --git a/cli/cli/semantics/unit.txx b/cli/cli/semantics/unit.txx new file mode 100644 index 0000000..99d178f --- /dev/null +++ b/cli/cli/semantics/unit.txx @@ -0,0 +1,108 @@ +// file : cli/semantics/unit.txx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +namespace semantics +{ + template <typename T> + T* cli_unit:: + lookup (std::string const& ss, std::string const& name, bool outer) + { + using std::string; + + // Resolve the starting scope in this unit, if any. + // + string::size_type b (0), e; + scope* s (0); + + do + { + e = ss.find ("::", b); + string n (ss, b, e == string::npos ? e : e - b); + + if (n.empty ()) + s = this; + else + { + scope::names_iterator_pair ip (s->find (n)); + + for (s = 0; ip.first != ip.second; ++ip.first) + if ((s = dynamic_cast<scope*> (&ip.first->named ()))) + break; + + if (s == 0) + break; // No such scope in this unit. + } + + b = e; + + if (b == string::npos) + break; + + b += 2; + } while (true); + + // If we have the starting scope, then try to resolve the name in it. + // + if (s != 0) + { + b = 0; + + do + { + e = name.find ("::", b); + string n (name, b, e == string::npos ? e : e - b); + + scope::names_iterator_pair ip (s->find (n)); + + // If this is the last name, then see if we have the desired type. + // + if (e == string::npos) + { + for (; ip.first != ip.second; ++ip.first) + if (T* r = dynamic_cast<T*> (&ip.first->named ())) + return r; + } + // Otherwise, this should be a scope. + // + else + { + for (s = 0; ip.first != ip.second; ++ip.first) + if ((s = dynamic_cast<scope*> (&ip.first->named ()))) + break; + + if (s == 0) + break; // No such inner scope. + } + + b = e; + + if (b == string::npos) + break; + + b += 2; + } while (true); + } + + // If we are here, then that means the lookup didn't find anything in + // this unit. The next step is to examine all the included units. + // + for (includes_iterator i (includes_begin ()); i != includes_end (); ++i) + { + if (cli_includes* ci = dynamic_cast<cli_includes*> (&*i)) + if (T* r = ci->includee ().lookup<T> (ss, name, false)) + return r; + } + + // If we still haven't found anything, then the next step is to search + // one-outer scope, unless it is the global namespace. + // + if (outer && !ss.empty ()) + { + string n (ss, 0, ss.rfind ("::")); + return lookup<T> (n, name, true); + } + + return 0; + } +} diff --git a/cli/cli/source.cxx b/cli/cli/source.cxx new file mode 100644 index 0000000..9cd2382 --- /dev/null +++ b/cli/cli/source.cxx @@ -0,0 +1,1374 @@ +// file : cli/source.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <iostream> + +#include <cli/source.hxx> +#include <cli/txt.hxx> // txt_size(), txt_wrap_lines() + +using namespace std; + +namespace +{ + struct option_init: traversal::option, context + { + option_init (context& c) : context (c), comma_ (false) {} + + virtual void + traverse (type& o) + { + if (comma_) + os << "," << endl + << " "; + else + { + os << endl + << ": "; + comma_ = true; + } + + os << emember (o); + + if (o.initialized_p ()) + { + using semantics::expression; + expression const& i (o.initializer ()); + + switch (i.type ()) + { + case expression::string_lit: + case expression::char_lit: + case expression::bool_lit: + case expression::int_lit: + case expression::float_lit: + case expression::identifier: + { + os << " (" << i.value () << ")"; + break; + } + case expression::call_expr: + { + os << " " << i.value (); + break; + } + } + } + else + os << " ()"; + + if (gen_specifier && o.type ().name () != "bool") + os << "," << endl + << " " << especifier_member (o) << " (false)"; + } + + private: + bool comma_; + }; + + struct option_merge: traversal::option, context + { + option_merge (context& c) : context (c) {} + + virtual void + traverse (type& o) + { + string type (o.type ().name ()); + bool b (type == "bool"); + + string member (emember (o)); + string spec_member (b ? member : especifier_member (o)); + + os << "if (a." << spec_member << ")" + << "{" + << cli << "::parser< " << type << ">::merge (" << endl + << "this->" << member << ", a." << member << ");"; + if (!b) + os << "this->" << spec_member << " = true;"; + os << "}"; + } + }; + + // + // + struct option_map: traversal::option, context + { + option_map (context& c) : context (c) {} + + virtual void + traverse (type& o) + { + using semantics::names; + + string member (emember (o)); + string type (o.type ().name ()); + string scope (escape (o.scope ().name ())); + string map ("_cli_" + scope + "_map_"); + + names& n (o.named ()); + + for (names::name_iterator i (n.name_begin ()); i != n.name_end (); ++i) + { + os << "_cli_" << scope << "_map_[\"" << *i << "\"] = " << endl + << "&" << cli << "::thunk< " << scope << ", " << type << ", " << + "&" << scope << "::" << member; + + if (gen_specifier && type != "bool") + os << "," << endl + << " &" << scope << "::" << especifier_member (o); + + os << " >;"; + } + } + }; + + // + // + struct option_desc: traversal::option, context + { + option_desc (context& c) : context (c) {} + + virtual void + traverse (type& o) + { + using semantics::names; + + names& n (o.named ()); + + os << "// " << o.name () << endl + << "//" << endl + << "{" + << cli << "::option_names a;"; + + for (names::name_iterator b (n.name_begin ()), i (b); + i != n.name_end (); ++i) + { + if (i == b) // Skip the primary name. + continue; + + os << "a.push_back (\"" << *i << "\");"; + } + + if (o.initialized_p ()) + { + using semantics::expression; + expression const& i (o.initializer ()); + + switch (i.type ()) + { + case expression::string_lit: + { + os << "std::string dv (" << i.value () << ");"; + break; + } + case expression::char_lit: + { + os << "std::string dv (1, " << i.value () << ");"; + break; + } + case expression::bool_lit: + case expression::int_lit: + case expression::float_lit: + { + os << "std::string dv (\"" << i.value () << "\");"; + break; + } + case expression::identifier: + case expression::call_expr: + { + os << "std::string dv;"; + break; + } + } + } + else + os << "std::string dv;"; + + + os << cli << "::option o (\"" << o.name () << "\", a, " << + (o.type ().name () == "bool" ? "true" : "false") << ", dv);" + << "os.push_back (o);" + << "}"; + } + }; + + static string + escape_str (string const& s) + { + string r; + + for (size_t i (0), n (s.size ()); i < n; ++i) + { + switch (s[i]) + { + case '\\': + { + r += "\\\\"; + break; + } + case '"': + { + r += "\\\""; + break; + } + case '\033': + { + r += "\\033"; + break; + } + default: + { + r += s[i]; + break; + } + } + } + + return r; + } + + inline void + wrap_lines (ostream& os, + string& d, + size_t indent = 0, + size_t first = 0) + { + txt_wrap_lines (os, + d, + indent, + first, + " << \"", // line_start + "\" << ::std::endl", // line_end + " << ::std::endl", // line_blank + &escape_str); + } + + enum paragraph {para_unknown, para_text, para_option}; + + struct doc: traversal::doc, context + { + doc (context& c, usage_type u, paragraph& p) + : context (c), usage_ (u), para_ (p) {} + + virtual void + traverse (type& ds) + { + if (ds.name ().compare (0, 3, "doc") != 0) // Ignore doc variables. + return; + + // Figure out which documentation string we should use. + // + // n = 1 - common doc string + // n = 2 - arg string, common doc string + // n > 2 - arg string, short string, long string + // + size_t n (ds.size ()); + string d; + + if (gen_usage == ut_both && usage_ == ut_long) + { + d = n > 2 // Have both short and long? + ? ds[2] // Then use long. + : (n == 1 ? ds[0] : ds[1]); // Else, use common. + } + else // Short or long. + { + d = n > 2 // Have both short and long? + ? ds[1] // Then use short, + : (n == 1 ? ds[0] : ds[1]); // Else, use common (no first sentence). + } + + std::set<string> arg_set; + if (n > 1 && options.ansi_color ()) + translate_arg (ds[0], arg_set); + + d = format (ds.scope (), translate (d, arg_set), true); + + if (d.empty ()) + return; + + string up (cli + "::usage_para"); + + if (para_ == para_unknown) + os << "if (p != " << up << "::none)" << endl + << "os << ::std::endl;" + << endl + << "os << \""; + else + os << "os << std::endl" << endl + << " << \""; + + wrap_lines (os, d); + os << ";" + << endl; + + para_ = para_text; + } + + private: + usage_type usage_; + paragraph& para_; + }; + + struct option_length: traversal::option, context + { + option_length (context& c, size_t& l) + : context (c), length_ (l), option_ (0) {} + + option_length (context& c, size_t& l, type*& o) + : context (c), length_ (l), option_ (&o) {} + + virtual void + traverse (type& o) + { + using semantics::names; + + semantics::doc_strings const& doc (o.doc ()); + + if (options.suppress_undocumented () && doc.empty ()) + return; + + size_t l (0); + names& n (o.named ()); + + for (names::name_iterator i (n.name_begin ()); i != n.name_end (); ++i) + { + if (l != 0) + l++; // '|' seperator. + + l += i->size (); + } + + string type (o.type ().name ()); + + if (type != "bool" || doc.size () >= 3) + { + l++; // ' ' seperator + + string s (doc.size () > 0 ? doc[0] : string ("<arg>")); + + if (options.ansi_color ()) + { + std::set<string> arg_set; + s = translate_arg (s, arg_set); + } + + l += txt_size (format (o.scope (), s, false)); + } + + if (l > length_) + { + length_ = l; + if (option_ != 0) + *option_ = &o; + } + } + + private: + size_t& length_; + type** option_; + }; + + // + // + struct option_usage: traversal::option, context + { + option_usage (context& c, size_t l, usage_type u, paragraph& p) + : context (c), length_ (l), usage_ (u), para_ (p) {} + + virtual void + traverse (type& o) + { + using semantics::names; + + semantics::doc_strings const& doc (o.doc ()); + + if (options.suppress_undocumented () && doc.empty ()) + return; + + bool color (options.ansi_color ()); + + size_t l (0); + names& n (o.named ()); + + string up (cli + "::usage_para"); + + // For long usage we want options separated by blank lines. + // + if (para_ == para_unknown) + { + if (usage_ == ut_long) + os << "if (p != " << up << "::none)" << endl; + else + os << "if (p == " << up << "::text)" << endl; + + os << "os << ::std::endl;" + << endl + << "os << \""; + } + else if (para_ == para_text || usage_ == ut_long) + os << "os << std::endl" << endl + << " << \""; + else + os << "os << \""; + + for (names::name_iterator i (n.name_begin ()); i != n.name_end (); ++i) + { + if (l != 0) + { + os << '|'; + l++; + } + + if (color) + os << "\\033[1m"; // Bold. + + os << escape_str (*i); + + if (color) + os << "\\033[0m"; + + l += i->size (); + } + + string type (o.type ().name ()); + + std::set<string> arg_set; + if (type != "bool" || doc.size () >= 3) + { + os << ' '; + l++; + + string s (doc.size () > 0 ? doc[0] : string ("<arg>")); + + if (color) + s = translate_arg (s, arg_set); + + s = format (o.scope (), s, false); + + os << escape_str (s); + l += txt_size (s); + } + + // Figure out which documentation string we should use. + // + string d; + { + size_t i (type == "bool" && doc.size () < 3 ? 0 : 1); + + if (doc.size () > i) // Have at least one. + { + if (gen_usage == ut_both && usage_ == ut_long) + { + d = doc.size () > i + 1 // Have both short and long? + ? doc[i + 1] // Then use long. + : doc[i]; + } + else // Short or long. + { + d = doc.size () > i + 1 // Have both short and long? + ? doc[i] // Then use short, + : (gen_usage == ut_long // Otherwise, if asked for long, + ? doc[i] // Then use long, + : first_sentence (doc[i])); // Else first sentence of long. + } + } + } + + // Format the documentation string. + // + if (color) + d = translate (d, arg_set); + + d = format (o.scope (), d, false); + + if (!d.empty ()) + wrap_lines (os, d, length_ + 1, l); // +1 for extra space after arg. + else + os << "\" << std::endl"; + + os << ";" + << endl; + + para_ = para_option; + } + + private: + size_t length_; + usage_type usage_; + paragraph& para_; + }; + + // + // + struct base_parse: traversal::class_, context + { + base_parse (context& c): context (c) {} + + virtual void + traverse (type& c) + { + os << "// " << escape (c.name ()) << " base" << endl + << "//" << endl + << "if (" << fq_name (c) << "::_parse (o, s))" << endl + << "return true;" + << endl; + } + }; + + // + // + struct base_merge: traversal::class_, context + { + base_merge (context& c): context (c) {} + + virtual void + traverse (type& c) + { + os << "// " << escape (c.name ()) << " base" << endl + << "//" << endl + << fq_name (c) << "::merge (a);" + << endl; + } + }; + + // + // + struct base_desc: traversal::class_, context + { + base_desc (context& c): context (c) {} + + virtual void + traverse (type& c) + { + os << "// " << escape (c.name ()) << " base" << endl + << "//" << endl + << fq_name (c) << "::fill (os);" + << endl; + } + }; + + struct base_usage: traversal::class_, context + { + base_usage (context& c, usage_type u): context (c), usage_ (u) {} + + virtual void + traverse (type& c) + { + class_doc_type cd (class_doc (c)); + + if (cd == cd_exclude || cd == cd_exclude_base) + return; + + const char* t ( + (cd == cd_default + ? gen_usage != ut_both || usage_ == ut_short + : cd == cd_short) ? "" : "long_"); + + os << "// " << escape (c.name ()) << " base" << endl + << "//" << endl + << "p = " << fq_name (c) << "::print_" << t << "usage (os, p);" + << endl; + } + + private: + usage_type usage_; + }; + + // + // + struct class_: traversal::class_, context + { + class_ (context& c) + : context (c), + base_parse_ (c), + base_merge_ (c), + base_desc_ (c), + option_merge_ (c), + option_map_ (c), + option_desc_ (c) + { + inherits_base_parse_ >> base_parse_; + inherits_base_merge_ >> base_merge_; + inherits_base_desc_ >> base_desc_; + names_option_merge_ >> option_merge_; + names_option_map_ >> option_map_; + names_option_desc_ >> option_desc_; + } + + virtual void + traverse (type& c) + { + string name (escape (c.name ())); + + bool abst (c.abstract ()); + bool ho (has<semantics::option> (c)); + bool hb (c.inherits_begin () != c.inherits_end ()); + + os << "// " << name << endl + << "//" << endl + << endl; + + // c-tors + // + string um (cli + "::unknown_mode"); + + os << name << "::" << endl + << name << " ()"; + { + option_init init (*this); + traversal::names names_init (init); + names (c, names_init); + } + os << "{" + << "}"; + + if (!abst) + { + bool p (gen_parse); + + string n, res, ret; + if (p) + { + n = "bool " + name + "::\n" + (name != "parse" ? "parse" : "parse_"); + res = "bool r = "; + ret = "return r;"; + } + else + n = name + "::\n" + name; + + os << n << " (int& argc," << endl + << "char** argv," << endl + << "bool erase," << endl + << um << " opt," << endl + << um << " arg)"; + if (!p) + { + option_init init (*this); + traversal::names names_init (init); + names (c, names_init); + } + os << "{" + << cli << "::argv_scanner s (argc, argv, erase);" + << res << "_parse (s, opt, arg);" + << ret + << "}"; + + os << n << " (int start," << endl + << "int& argc," << endl + << "char** argv," << endl + << "bool erase," << endl + << um << " opt," << endl + << um << " arg)"; + if (!p) + { + option_init init (*this); + traversal::names names_init (init); + names (c, names_init); + } + os << "{" + << cli << "::argv_scanner s (start, argc, argv, erase);" + << res << "_parse (s, opt, arg);" + << ret + << "}"; + + os << n << " (int& argc," << endl + << "char** argv," << endl + << "int& end," << endl + << "bool erase," << endl + << um << " opt," << endl + << um << " arg)"; + if (!p) + { + option_init init (*this); + traversal::names names_init (init); + names (c, names_init); + } + os << "{" + << cli << "::argv_scanner s (argc, argv, erase);" + << res << "_parse (s, opt, arg);" + << "end = s.end ();" + << ret + << "}"; + + os << n << " (int start," << endl + << "int& argc," << endl + << "char** argv," << endl + << "int& end," << endl + << "bool erase," << endl + << um << " opt," << endl + << um << " arg)"; + if (!p) + { + option_init init (*this); + traversal::names names_init (init); + names (c, names_init); + } + os << "{" + << cli << "::argv_scanner s (start, argc, argv, erase);" + << res << "_parse (s, opt, arg);" + << "end = s.end ();" + << ret + << "}"; + + os << n << " (" << cli << "::scanner& s," << endl + << um << " opt," << endl + << um << " arg)"; + if (!p) + { + option_init init (*this); + traversal::names names_init (init); + names (c, names_init); + } + os << "{" + << res << "_parse (s, opt, arg);" + << ret + << "}"; + } + + // merge() + // + if (gen_merge) + { + os << "void " << name << "::" << endl + << "merge (const " << name << "& a)" + << "{" + << "CLI_POTENTIALLY_UNUSED (a);" + << endl; + + // First merge all our bases. + // + inherits (c, inherits_base_merge_); + + // Then our options. + // + names (c, names_option_merge_); + + os << "}"; + } + + // Usage. + // + if (gen_usage != ut_none) + { + bool b (hb && !options.exclude_base ()); + + // Calculate option length. + // + size_t len (0); + { + semantics::option* o (0); + size_t max (options.option_length ()); + + // We need to go into our bases unless --exclude-base was + // specified. + // + { + traversal::class_ ct; + option_length olt (*this, len, o); + traversal::inherits i; + traversal::names n; + + if (b) + ct >> i >> ct; + + ct >> n >> olt; + ct.traverse (c); + + // Now do the same for each base and issue a warning if any + // base has shorter option length than derived. + // + if (b && max == 0) + { + size_t d_len (len); + semantics::option* d_o (o); + + for (type::inherits_iterator i (c.inherits_begin ()); + i != c.inherits_end (); ++i) + { + type& b (i->base ()); + + len = 0; + ct.traverse (b); + + if (len == d_len) + continue; + + cerr << c.file () << ":" << c.line () << ":" << c.column () + << " warning: derived class option length is greater " + << "than that of a base class '" << b.name () << "'" + << endl; + + cerr << b.file () << ":" << b.line () << ":" << b.column () + << " note: class '" << b.name () << "' is defined here" + << endl; + + cerr << c.file () << ":" << c.line () << ":" << c.column () + << " note: use --option-length to specify uniform length" + << endl; + } + + len = d_len; + o = d_o; + } + } + + if (len != 0 && max != 0) + { + if (len > max) + { + cerr << o->file () << ":" << o->line () << ":" << o->column () + << " error: option length " << len << " is greater than " + << max << " specified with --option-length" << endl; + throw generation_failed (); + } + + len = max; + } + + verify_id_ref (); + } + + string up (cli + "::usage_para"); + string const& ost (options.ostream_type ()); + + os << up << " " << name << "::" << endl + << "print_usage (" << ost << "& os, " << up << " p)" + << "{" + << "CLI_POTENTIALLY_UNUSED (os);" + << endl; + { + usage_type u (gen_usage == ut_both ? ut_short : gen_usage); + + base_usage bu (*this, u); + traversal::inherits i (bu); + + if (b && !options.include_base_last ()) + inherits (c, i); + + paragraph p (para_unknown); + + doc dc (*this, u, p); + option_usage ou (*this, len, u, p); + traversal::names n; + n >> dc; + n >> ou; + + names (c, n); + + if (p != para_unknown) + os << "p = " << up << (p == para_text ? "::text;" : "::option;") + << endl; + + if (b && options.include_base_last ()) + inherits (c, i); + } + + os << "return p;" + << "}"; + + verify_id_ref (); + + // Long version. + // + if (gen_usage == ut_both) + { + os << up << " " << name << "::" << endl + << "print_long_usage (" << ost << "& os, " << up << " p)" + << "{" + << "CLI_POTENTIALLY_UNUSED (os);" + << endl; + + base_usage bu (*this, ut_long); + traversal::inherits i (bu); + + if (b && !options.include_base_last ()) + inherits (c, i); + + paragraph p (para_unknown); + + doc dc (*this, ut_long, p); + option_usage ou (*this, len, ut_long, p); + traversal::names n; + n >> dc; + n >> ou; + + names (c, n); + + if (p != para_unknown) + os << "p = " << up << (p == para_text ? "::text;" : "::option;") + << endl; + + if (b && options.include_base_last ()) + inherits (c, i); + + os << "return p;" + << "}"; + + verify_id_ref (); + } + } + + // Description. + // + if (options.generate_description ()) + { + string desc ("_cli_" + name + "_desc"); + + os << "struct " << desc << "_type: " << cli << "::options" + << "{" + << desc << "_type ()" + << "{" + << fq_name (c) << "::fill (*this);" + << "}" + << "};"; + + if (options.std () < cxx_version::cxx11) + os << "static " << desc << "_type " << desc << "_;" + << endl; + + os << "void " << name << "::" << endl + << "fill (" << cli << "::options&" << (ho || hb ? " os)" : ")") + << "{"; + + // Add the entries from our bases first so that our entires + // override any conflicts. + // + inherits (c, inherits_base_desc_); + names (c, names_option_desc_); + + os << "}"; + + os << "const " << cli << "::options& " << name << "::" << endl + << "description ()" + << "{"; + + if (options.std () >= cxx_version::cxx11) + os << "static " << desc << "_type " << desc << "_;"; + + os << "return " << desc << "_;" + << "}"; + } + + // _parse () + // + string map ("_cli_" + name + "_map"); + + os << "typedef" << endl + << "std::map<std::string, void (*) (" << + name << "&, " << cli << "::scanner&)>" << endl + << map << ";" + << endl + << "static " << map << " " << map << "_;" + << endl; + + os << "struct " << map << "_init" + << "{" + << map << "_init ()" + << "{"; + + names (c, names_option_map_); + + os << "}" + << "};" + << "static " << map << "_init " << map << "_init_;" + << endl; + + os << "bool " << name << "::" << endl + << "_parse (const char* o, " << cli << "::scanner& s)" + << "{" + << map << "::const_iterator i (" << map << "_.find (o));" + << endl + << "if (i != " << map << "_.end ())" + << "{" + << "(*(i->second)) (*this, s);" + << "return true;" + << "}"; + + // Try our bases, from left-to-right. + // + inherits (c, inherits_base_parse_); + + os << "return false;" + << "}"; + + if (!abst) + { + bool pfx (!opt_prefix.empty ()); + bool sep (!opt_sep.empty ()); + + bool comb_flags (pfx && !options.no_combined_flags ()); + bool comb_values (pfx && !options.no_combined_values ()); + + os << "bool " << name << "::" << endl + << "_parse (" << cli << "::scanner& s," << endl + << um << (pfx ? " opt_mode" : "") << "," << endl + << um << " arg_mode)" + << "{"; + + if (comb_flags) + os << "// Can't skip combined flags (--no-combined-flags)." << endl + << "//" << endl + << "assert (opt_mode != " << cli << "::unknown_mode::skip);" + << endl; + + os << "bool r = false;"; + + if (sep) + os << "bool opt = true;" // Still recognizing options. + << endl; + + os << "while (s.more ())" + << "{" + << "const char* o = s.peek ();"; + + if (sep) + { + os << endl + << "if (std::strcmp (o, \"" << opt_sep << "\") == 0)" + << "{" + << "opt = false;"; + if (!options.keep_separator ()) + { + os << "s.skip ();" // We don't want to erase the separator. + << "r = true;" + << "continue;"; + } + os << "}"; + } + + if (sep) + os << "if (opt)" + << "{"; + + // First try the argument as is. + // + os << "if (_parse (o, s))" + << "{" + << "r = true;" + << "continue;" + << "}"; + + if (pfx) + { + size_t n (opt_prefix.size ()); + + os << "if (std::strncmp (o, \"" << opt_prefix << "\", " << + n << ") == 0 && o[" << n << "] != '\\0')" + << "{"; + + if (comb_values) + { + os << "// Handle combined option values." << endl + << "//" << endl + << "std::string co;" // Need to live until next block. + << "if (const char* v = std::strchr (o, '='))" + << "{" + << "co.assign (o, 0, v - o);" + << "++v;" + << endl + << "int ac (2);" + << "char* av[] =" + << "{" + << "const_cast<char*> (co.c_str ())," << endl + << "const_cast<char*> (v)" + << "};" + << cli << "::argv_scanner ns (0, ac, av);" + << endl + << "if (_parse (co.c_str (), ns))" + << "{" + << "// Parsed the option but not its value?" << endl + << "//" << endl + << "if (ns.end () != 2)" << endl + << "throw " << cli << "::invalid_value (co, v);" + << endl + << "s.next ();" + << "r = true;" + << "continue;" + << "}" + << "else" + << "{" + << "// Set the unknown option and fall through." << endl + << "//" << endl + << "o = co.c_str ();" + << "}" + << "}"; + } + + if (comb_flags) + { + os << "// Handle combined flags." << endl + << "//" << endl + << "char cf[" << n + 2 << "];" // Need to live until next block. + << "{" + << "const char* p = o + " << n << ";" + << "for (; *p != '\\0'; ++p)" + << "{" + << "if (!((*p >= 'a' && *p <= 'z') ||" << endl + << "(*p >= 'A' && *p <= 'Z') ||" << endl + << "(*p >= '0' && *p <= '9')))" << endl + << "break;" + << "}" + << "if (*p == '\\0')" + << "{" + << "for (p = o + " << n << "; *p != '\\0'; ++p)" + << "{" + << "std::strcpy (cf, \"" << opt_prefix << "\");" + << "cf[" << n << "] = *p;" + << "cf[" << n + 1 << "] = '\\0';" + << endl + << "int ac (1);" + << "char* av[] = {cf};" + << cli << "::argv_scanner ns (0, ac, av);" + << endl + << "if (!_parse (cf, ns))" << endl + << "break;" + << "}" + << "if (*p == '\\0')" + << "{" + << "// All handled." << endl + << "//" << endl + << "s.next ();" + << "r = true;" + << "continue;" + << "}" + << "else" + << "{" + << "// Set the unknown option and fall through." << endl + << "//" << endl + << "o = cf;" + << "}" + << "}" + << "}"; + } + + // Unknown option. + // + os << "switch (opt_mode)" + << "{" + << "case " << cli << "::unknown_mode::skip:" << endl + << "{" + << "s.skip ();" + << "r = true;" + << "continue;" + << "}" + << "case " << cli << "::unknown_mode::stop:" << endl + << "{" + << "break;" + << "}" + << "case " << cli << "::unknown_mode::fail:" << endl + << "{" + << "throw " << cli << "::unknown_option (o);" + << "}" + << "}" // switch + << "break;" // The stop case. + << "}"; + } + + if (sep) + os << "}"; + + // Unknown argument. + // + os << "switch (arg_mode)" + << "{" + << "case " << cli << "::unknown_mode::skip:" << endl + << "{" + << "s.skip ();" + << "r = true;" + << "continue;" + << "}" + << "case " << cli << "::unknown_mode::stop:" << endl + << "{" + << "break;" + << "}" + << "case " << cli << "::unknown_mode::fail:" << endl + << "{" + << "throw " << cli << "::unknown_argument (o);" + << "}" + << "}" // switch + << "break;" // The stop case. + + << "}" // for + << "return r;" + << "}"; + } + } + + private: + base_parse base_parse_; + traversal::inherits inherits_base_parse_; + + base_merge base_merge_; + traversal::inherits inherits_base_merge_; + + base_desc base_desc_; + traversal::inherits inherits_base_desc_; + + option_merge option_merge_; + traversal::names names_option_merge_; + + option_map option_map_; + traversal::names names_option_map_; + + option_desc option_desc_; + traversal::names names_option_desc_; + }; + + // Page usage. + // + struct class_usage: traversal::class_, context + { + class_usage (context& c, usage_type u, paragraph& p) + : context (c), usage_ (u), para_ (p) {} + + virtual void + traverse (type& c) + { + class_doc_type cd (class_doc (c)); + + if (cd == cd_exclude) + return; + + const char* t ( + (cd == cd_default || cd == cd_exclude_base + ? gen_usage != ut_both || usage_ == ut_short + : cd == cd_short) ? "" : "long_"); + + string p ( + para_ == para_unknown + ? "p" + : cli + "::usage_para::" + (para_ == para_text ? "text" : "option")); + + os << "p = " << fq_name (c) << "::print_" << t << "usage (os, " << + p << ");" + << endl; + + para_ = para_unknown; + } + + private: + usage_type usage_; + paragraph& para_; + }; +} + +void +generate_source (context& ctx) +{ + ostream& os (ctx.os); + + os << "#include <map>" << endl + << "#include <cstring>" << endl + << endl; + + traversal::cli_unit unit; + traversal::names unit_names; + namespace_ ns (ctx); + class_ cl (ctx); + + unit >> unit_names >> ns; + unit_names >> cl; + + traversal::names ns_names; + + ns >> ns_names >> ns; + ns_names >> cl; + + unit.dispatch (ctx.unit); + + // Entire page usage. + // + if (ctx.gen_usage != ut_none && ctx.options.page_usage_specified ()) + { + const string& qn (ctx.options.page_usage ()); + string n (ctx.escape (ctx.substitute (ctx.ns_open (qn, false)))); + + usage u (ctx.gen_usage); + string up (ctx.cli + "::usage_para"); + string const& ost (ctx.options.ostream_type ()); + + { + os << up << endl + << n << "usage (" << ost << "& os, " << up << " p)" + << "{" + << "CLI_POTENTIALLY_UNUSED (os);" + << endl; + + paragraph p (para_unknown); + + traversal::cli_unit unit; + traversal::names unit_names; + traversal::namespace_ ns; + doc dc (ctx, u == ut_both ? ut_short : u, p); + class_usage cl (ctx, u == ut_both ? ut_short : u, p); + + unit >> unit_names; + unit_names >> dc; + unit_names >> ns; + unit_names >> cl; + + traversal::names ns_names; + + ns >> ns_names; + ns_names >> dc; + ns_names >> ns; + ns_names >> cl; + + unit.dispatch (ctx.unit); + + if (p != para_unknown) + os << "p = " << up << (p == para_text ? "::text;" : "::option;") + << endl; + + os << "return p;" + << "}"; + + ctx.verify_id_ref (); + } + + // Long version. + // + if (u == ut_both) + { + os << up << endl + << n << "long_usage (" << ost << "& os, " << up << " p)" + << "{" + << "CLI_POTENTIALLY_UNUSED (os);" + << endl; + + paragraph p (para_unknown); + + traversal::cli_unit unit; + traversal::names unit_names; + traversal::namespace_ ns; + doc dc (ctx, ut_long, p); + class_usage cl (ctx, ut_long, p); + + unit >> unit_names; + unit_names >> dc; + unit_names >> ns; + unit_names >> cl; + + traversal::names ns_names; + + ns >> ns_names; + ns_names >> dc; + ns_names >> ns; + ns_names >> cl; + + unit.dispatch (ctx.unit); + + if (p != para_unknown) + os << "p = " << up << (p == para_text ? "::text;" : "::option;") + << endl; + + os << "return p;" + << "}"; + + ctx.verify_id_ref (); + } + + ctx.ns_close (qn, false); + } +} diff --git a/cli/cli/source.hxx b/cli/cli/source.hxx new file mode 100644 index 0000000..b27cf76 --- /dev/null +++ b/cli/cli/source.hxx @@ -0,0 +1,13 @@ +// file : cli/source.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_SOURCE_HXX +#define CLI_SOURCE_HXX + +#include <cli/context.hxx> + +void +generate_source (context&); + +#endif // CLI_SOURCE_HXX diff --git a/cli/cli/token.hxx b/cli/cli/token.hxx new file mode 100644 index 0000000..7045826 --- /dev/null +++ b/cli/cli/token.hxx @@ -0,0 +1,135 @@ +// file : cli/token.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_TOKEN_HXX +#define CLI_TOKEN_HXX + +#include <string> +#include <cstddef> // std::size_t + +class token +{ +public: + enum token_type + { + t_eos, + t_keyword, + t_identifier, + t_punctuation, + t_cxx_path_lit, + t_cli_path_lit, + t_string_lit, + t_char_lit, + t_bool_lit, + t_int_lit, + t_float_lit, + t_call_expr, // The so called "call expression", e.g., (2, a). + t_template_expr // The so called "template expression", e.g., <foo, 3>. + }; + + token_type + type () const; + + std::size_t + line () const; + + std::size_t + column () const; + + // Keyword + // +public: + enum keyword_type + { + k_source, + k_include, + k_namespace, + k_class, + k_signed, + k_unsigned, + k_bool, + k_char, + k_wchar, + k_short, + k_int, + k_long, + k_float, + k_double, + k_invalid + }; + + // Return the keyword id if type is t_keyword and k_invalid otherwise. + // + keyword_type + keyword () const; + + // Identifier + // +public: + std::string const& + identifier () const; + + // Punctuation + // +public: + enum punctuation_type + { + p_semi, + p_comma, + p_colon, + p_dcolon, + p_lcbrace, + p_rcbrace, + // p_lparen, + // p_rparen, + p_eq, + p_or, + p_invalid + }; + + // Return the punctuation id if type is t_punctuation and p_invalid + // otherwise. + // + punctuation_type + punctuation () const; + + // Literals. + // +public: + std::string const& + literal () const; + + // Expressions. + // +public: + std::string const& + expression () const; + + // C-tors. + // +public: + // EOS. + // + token (std::size_t l, std::size_t c); + + token (keyword_type k, std::size_t l, std::size_t c); + token (punctuation_type p, std::size_t l, std::size_t c); + + // Identifier, literals, and expressions. + // + token (token_type t, std::string const& s, std::size_t l, std::size_t c); + +private: + std::size_t l_; + std::size_t c_; + + token_type type_; + keyword_type keyword_; + punctuation_type punctuation_; + std::string str_; +}; + +#include <cli/token.ixx> + +#endif // CLI_TOKEN_HXX diff --git a/cli/cli/token.ixx b/cli/cli/token.ixx new file mode 100644 index 0000000..77ab225 --- /dev/null +++ b/cli/cli/token.ixx @@ -0,0 +1,88 @@ +// file : cli/token.ixx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +inline token::token_type token:: +type () const +{ + return type_; +} + +inline std::size_t token:: +line () const +{ + return l_; +} + +inline std::size_t token:: +column () const +{ + return c_; +} + +inline token::keyword_type token:: +keyword () const +{ + return type_ == t_keyword ? keyword_ : k_invalid; +} + +inline std::string const& token:: +identifier () const +{ + return str_; +} + +inline token::punctuation_type token:: +punctuation () const +{ + return type_ == t_punctuation ? punctuation_ : p_invalid; +} + +inline std::string const& token:: +literal () const +{ + return str_; +} + +inline std::string const& token:: +expression () const +{ + return str_; +} + +inline token:: +token (std::size_t l, std::size_t c) + : l_ (l), c_ (c), + type_ (t_eos), + keyword_ (k_invalid), + punctuation_ (p_invalid) +{ +} + +inline token:: +token (keyword_type k, std::size_t l, std::size_t c) + : l_ (l), c_ (c), + type_ (t_keyword), + keyword_ (k), + punctuation_ (p_invalid) +{ +} + +inline token:: +token (punctuation_type p, std::size_t l, std::size_t c) + : l_ (l), c_ (c), + type_ (t_punctuation), + keyword_ (k_invalid), + punctuation_ (p) +{ +} + +inline token:: +token (token_type t, std::string const& s, std::size_t l, std::size_t c) + : l_ (l), c_ (c), + type_ (t), + keyword_ (k_invalid), + punctuation_ (p_invalid), + str_ (s) +{ +} diff --git a/cli/cli/traversal.hxx b/cli/cli/traversal.hxx new file mode 100644 index 0000000..1961f7b --- /dev/null +++ b/cli/cli/traversal.hxx @@ -0,0 +1,16 @@ +// file : cli/traversal.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_TRAVERSAL_HXX +#define CLI_TRAVERSAL_HXX + +#include <cli/traversal/class.hxx> +#include <cli/traversal/doc.hxx> +#include <cli/traversal/elements.hxx> +#include <cli/traversal/expression.hxx> +#include <cli/traversal/namespace.hxx> +#include <cli/traversal/option.hxx> +#include <cli/traversal/unit.hxx> + +#endif // CLI_TRAVERSAL_HXX diff --git a/cli/cli/traversal/class.cxx b/cli/cli/traversal/class.cxx new file mode 100644 index 0000000..b920f1f --- /dev/null +++ b/cli/cli/traversal/class.cxx @@ -0,0 +1,49 @@ +// file : cli/traversal/class.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <cli/traversal/class.hxx> + +namespace traversal +{ + // inherits + // + void inherits:: + traverse (type& i) + { + dispatch (i.base ()); + } + + // class_ + // + void class_:: + traverse (type& c) + { + pre (c); + inherits (c); + names (c); + post (c); + } + + void class_:: + inherits (type& c) + { + inherits (c, *this); + } + + void class_:: + inherits (type& c, edge_dispatcher& d) + { + iterate_and_dispatch (c.inherits_begin (), c.inherits_end (), d); + } + + void class_:: + pre (type&) + { + } + + void class_:: + post (type&) + { + } +} diff --git a/cli/cli/traversal/class.hxx b/cli/cli/traversal/class.hxx new file mode 100644 index 0000000..38cbefc --- /dev/null +++ b/cli/cli/traversal/class.hxx @@ -0,0 +1,41 @@ +// file : cli/traversal/class.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_TRAVERSAL_CLASS_HXX +#define CLI_TRAVERSAL_CLASS_HXX + +#include <cli/traversal/elements.hxx> +#include <cli/semantics/class.hxx> + +namespace traversal +{ + struct inherits: edge<semantics::inherits> + { + inherits () {} + inherits (node_dispatcher& n) {node_traverser (n);} + + virtual void + traverse (type&); + }; + + struct class_: scope_template<semantics::class_> + { + virtual void + traverse (type&); + + virtual void + pre (type&); + + virtual void + inherits (type&); + + virtual void + inherits (type&, edge_dispatcher&); + + virtual void + post (type&); + }; +} + +#endif // CLI_TRAVERSAL_CLASS_HXX diff --git a/cli/cli/traversal/doc.hxx b/cli/cli/traversal/doc.hxx new file mode 100644 index 0000000..70a6dfd --- /dev/null +++ b/cli/cli/traversal/doc.hxx @@ -0,0 +1,16 @@ +// file : cli/traversal/doc.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_TRAVERSAL_DOC_HXX +#define CLI_TRAVERSAL_DOC_HXX + +#include <cli/traversal/elements.hxx> +#include <cli/semantics/doc.hxx> + +namespace traversal +{ + struct doc: node<semantics::doc> {}; +} + +#endif // CLI_TRAVERSAL_DOC_HXX diff --git a/cli/cli/traversal/elements.cxx b/cli/cli/traversal/elements.cxx new file mode 100644 index 0000000..f3353f2 --- /dev/null +++ b/cli/cli/traversal/elements.cxx @@ -0,0 +1,14 @@ +// file : cli/traversal/elements.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <cli/traversal/elements.hxx> + +namespace traversal +{ + void names:: + traverse (type& e) + { + dispatch (e.named ()); + } +} diff --git a/cli/cli/traversal/elements.hxx b/cli/cli/traversal/elements.hxx new file mode 100644 index 0000000..a2ada23 --- /dev/null +++ b/cli/cli/traversal/elements.hxx @@ -0,0 +1,142 @@ +// file : cli/traversal/elements.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_TRAVERSAL_ELEMENTS_HXX +#define CLI_TRAVERSAL_ELEMENTS_HXX + +#include <cutl/compiler/traversal.hxx> + +#include <cli/semantics/elements.hxx> + +namespace traversal +{ + using namespace cutl; + + // + // + typedef compiler::dispatcher<semantics::node> node_dispatcher; + typedef compiler::dispatcher<semantics::edge> edge_dispatcher; + + // + // + struct node_base: node_dispatcher, edge_dispatcher + { + void + edge_traverser (edge_dispatcher& d) + { + edge_dispatcher::traverser (d); + } + + edge_dispatcher& + edge_traverser () + { + return *this; + } + + using node_dispatcher::dispatch; + using edge_dispatcher::dispatch; + + using edge_dispatcher::iterate_and_dispatch; + }; + + struct edge_base: edge_dispatcher, node_dispatcher + { + void + node_traverser (node_dispatcher& d) + { + node_dispatcher::traverser (d); + } + + node_dispatcher& + node_traverser () + { + return *this; + } + + using edge_dispatcher::dispatch; + using node_dispatcher::dispatch; + + using node_dispatcher::iterate_and_dispatch; + }; + + inline edge_base& + operator>> (node_base& n, edge_base& e) + { + n.edge_traverser (e); + return e; + } + + inline node_base& + operator>> (edge_base& e, node_base& n) + { + e.node_traverser (n); + return n; + } + + // + // + template <typename X> + struct node: compiler::traverser_impl<X, semantics::node>, + virtual node_base + { + }; + + template <typename X> + struct edge: compiler::traverser_impl<X, semantics::edge>, + virtual edge_base + { + }; + + // Edges + // + + struct names: edge<semantics::names> + { + names () + { + } + + names (node_dispatcher& n) + { + node_traverser (n); + } + + virtual void + traverse (type&); + }; + + // Nodes + // + + struct nameable: node<semantics::nameable> {}; + + template <typename T> + struct scope_template: node<T> + { + public: + virtual void + traverse (T& s) + { + names (s); + } + + virtual void + names (T& s) + { + names (s, *this); + } + + virtual void + names (T& s, edge_dispatcher& d) + { + this->iterate_and_dispatch (s.names_begin (), s.names_end (), d); + } + }; + + typedef scope_template<semantics::scope> scope; + + struct type: node<semantics::type> {}; +} + +#endif // CLI_TRAVERSAL_ELEMENTS_HXX diff --git a/cli/cli/traversal/expression.hxx b/cli/cli/traversal/expression.hxx new file mode 100644 index 0000000..0888455 --- /dev/null +++ b/cli/cli/traversal/expression.hxx @@ -0,0 +1,16 @@ +// file : cli/traversal/expression.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_TRAVERSAL_EXPRESSION_HXX +#define CLI_TRAVERSAL_EXPRESSION_HXX + +#include <cli/traversal/elements.hxx> +#include <cli/semantics/expression.hxx> + +namespace traversal +{ + struct expression: node<semantics::expression> {}; +} + +#endif // CLI_TRAVERSAL_EXPRESSION_HXX diff --git a/cli/cli/traversal/namespace.cxx b/cli/cli/traversal/namespace.cxx new file mode 100644 index 0000000..c938f77 --- /dev/null +++ b/cli/cli/traversal/namespace.cxx @@ -0,0 +1,26 @@ +// file : cli/traversal/namespace.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <cli/traversal/namespace.hxx> + +namespace traversal +{ + void namespace_:: + traverse (type& n) + { + pre (n); + names (n); + post (n); + } + + void namespace_:: + pre (type&) + { + } + + void namespace_:: + post (type&) + { + } +} diff --git a/cli/cli/traversal/namespace.hxx b/cli/cli/traversal/namespace.hxx new file mode 100644 index 0000000..5709f2a --- /dev/null +++ b/cli/cli/traversal/namespace.hxx @@ -0,0 +1,26 @@ +// file : cli/traversal/namespace.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_TRAVERSAL_NAMESPACE_HXX +#define CLI_TRAVERSAL_NAMESPACE_HXX + +#include <cli/traversal/elements.hxx> +#include <cli/semantics/namespace.hxx> + +namespace traversal +{ + struct namespace_: scope_template<semantics::namespace_> + { + virtual void + traverse (type&); + + virtual void + pre (type&); + + virtual void + post (type&); + }; +} + +#endif // CLI_TRAVERSAL_NAMESPACE_HXX diff --git a/cli/cli/traversal/option.cxx b/cli/cli/traversal/option.cxx new file mode 100644 index 0000000..ff5dcce --- /dev/null +++ b/cli/cli/traversal/option.cxx @@ -0,0 +1,59 @@ +// file : cli/traversal/option.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <cli/traversal/option.hxx> +#include <cli/traversal/expression.hxx> + +namespace traversal +{ + // belongs + // + void belongs:: + traverse (type& e) + { + dispatch (e.type ()); + } + + // initialized + // + void initialized:: + traverse (type& e) + { + dispatch (e.expression ()); + } + + // option + // + void option:: + traverse (type& o) + { + pre (o); + belongs (o); + if (o.initialized_p ()) + initialized (o); + post (o); + } + + void option:: + pre (type&) + { + } + + void option:: + belongs (type& o) + { + belongs (o, edge_traverser ()); + } + + void option:: + initialized (type& o) + { + initialized (o, edge_traverser ()); + } + + void option:: + post (type&) + { + } +} diff --git a/cli/cli/traversal/option.hxx b/cli/cli/traversal/option.hxx new file mode 100644 index 0000000..e11fa21 --- /dev/null +++ b/cli/cli/traversal/option.hxx @@ -0,0 +1,74 @@ +// file : cli/traversal/option.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_TRAVERSAL_OPTION_HXX +#define CLI_TRAVERSAL_OPTION_HXX + +#include <cli/traversal/elements.hxx> +#include <cli/semantics/option.hxx> + +namespace traversal +{ + struct belongs: edge<semantics::belongs> + { + belongs () + { + } + + belongs (node_dispatcher& n) + { + node_traverser (n); + } + + virtual void + traverse (type&); + }; + + struct initialized: edge<semantics::initialized> + { + initialized () + { + } + + initialized (node_dispatcher& n) + { + node_traverser (n); + } + + virtual void + traverse (type&); + }; + + struct option: node<semantics::option> + { + virtual void + traverse (type&); + + virtual void + pre (type&); + + virtual void + belongs (type&); + + virtual void + initialized (type&); + + virtual void + post (type&); + + void + belongs (type& o, edge_dispatcher& d) + { + d.dispatch (o.belongs ()); + } + + void + initialized (type& o, edge_dispatcher& d) + { + d.dispatch (o.initialized ()); + } + }; +} + +#endif // CLI_TRAVERSAL_OPTION_HXX diff --git a/cli/cli/traversal/unit.cxx b/cli/cli/traversal/unit.cxx new file mode 100644 index 0000000..cf10d1d --- /dev/null +++ b/cli/cli/traversal/unit.cxx @@ -0,0 +1,46 @@ +// file : cli/traversal/unit.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <cli/traversal/unit.hxx> + +namespace traversal +{ + // cxx_includes + // + void cxx_includes:: + traverse (type& e) + { + dispatch (e.includee ()); + } + + // cli_includes + // + void cli_includes:: + traverse (type& e) + { + dispatch (e.includee ()); + } + + // cli_unit + // + void cli_unit:: + traverse (type& u) + { + pre (u); + iterate_and_dispatch ( + u.includes_begin (), u.includes_end (), edge_traverser ()); + names (u); + post (u); + } + + void cli_unit:: + pre (type&) + { + } + + void cli_unit:: + post (type&) + { + } +} diff --git a/cli/cli/traversal/unit.hxx b/cli/cli/traversal/unit.hxx new file mode 100644 index 0000000..eab42aa --- /dev/null +++ b/cli/cli/traversal/unit.hxx @@ -0,0 +1,58 @@ +// file : cli/traversal/unit.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_TRAVERSAL_UNIT_HXX +#define CLI_TRAVERSAL_UNIT_HXX + +#include <cli/traversal/elements.hxx> +#include <cli/semantics/unit.hxx> + +namespace traversal +{ + struct cxx_includes: edge<semantics::cxx_includes> + { + cxx_includes () + { + } + + cxx_includes (node_dispatcher& n) + { + node_traverser (n); + } + + virtual void + traverse (type&); + }; + + struct cli_includes: edge<semantics::cli_includes> + { + cli_includes () + { + } + + cli_includes (node_dispatcher& n) + { + node_traverser (n); + } + + virtual void + traverse (type&); + }; + + struct cxx_unit: node<semantics::cxx_unit> {}; + + struct cli_unit: scope_template<semantics::cli_unit> + { + virtual void + traverse (type&); + + virtual void + pre (type&); + + virtual void + post (type&); + }; +} + +#endif // CLI_TRAVERSAL_UNIT_HXX diff --git a/cli/cli/txt.cxx b/cli/cli/txt.cxx new file mode 100644 index 0000000..16de45a --- /dev/null +++ b/cli/cli/txt.cxx @@ -0,0 +1,301 @@ +// file : cli/txt.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#include <vector> +#include <cstring> // strlen() +#include <iostream> + +#include <cli/txt.hxx> + +using namespace std; + +size_t +txt_size (const string& s, size_t p, size_t n) +{ + size_t r (0); + + n = n == string::npos ? s.size () : n + p; + + // The start position (p) might be pointing half-way into the + // escape sequence. So we always have to scan from the start. + // + for (size_t i (0), m (s.size ()); i < n; ++i) + { + if (s[i] == '\033') // ANSI escape: "\033[Nm" + { + i += 3; + assert (i < m && s[i] == 'm'); + continue; + } + + if (i >= p) + ++r; + } + + return r; +} + +void +txt_wrap_lines (ostream& os, + string& d, + size_t indent, + size_t first, + const char* line_start, + const char* line_end, + const char* line_blank, + string (*escape) (string const&)) +{ + assert (!d.empty ()); + + os << string (indent - first, ' '); + + // Count the number of leading spaces at the beginning of each line. + // Then use it as an extra indentation if we are breaking this line. + // This makes multi-line definition lists look decent. Note that this + // doesn't work for ordered/unordered lists because they start on the + // same line as number/bullet. However, there is hack to make it work: + // break the first line at the 80 characters boundary explicitly with + // \n. + // + size_t wc (0), wi (0); // Count and count-based indentation. + bool cws (true); // Count flag. + + // @@ TODO: while this should support notes inside lists, lists inside + // notes will require splitting indentation into before/after '|'. + // + const char* ne (""); // Note. + const char* np (""); // Note prefix. + + size_t b (0), e (0), i (0); + for (size_t n (d.size ()); i < n; ++i) + { + char& c (d[i]); + + // Translate note character. + // + if (c == 0x07) + { + c = '|'; + ne = "| "; + } + + if (c == ' ' || c == '\n') + e = i; + + if (c == '\n' || txt_size (d, b, i - b) == 79 - indent - wi - strlen (np)) + { + if (b != 0) // Not a first line. + os << endl + << line_start << string (indent + wi, ' ') << np; + + string s (d, b, (e != b ? e : i) - b); + + if (escape != 0) + s = escape (s); + + os << s << line_end; + + // Handle consecutive newlines (e.g., pre, paragraph separator). + // + if (c == '\n') + { + for (; i + 1 < n && d[i + 1] == '\n'; e = ++i) + os << endl + << line_blank; + } + + b = e = (e != b ? e : i) + 1; + + // Start indenting/noting beginning with the next break. + // + wi = wc; + np = ne; + } + + if (c == '\n') + { + // Reset counters, clear notes, and start counting. + // + wc = wi = 0; + ne = np = ""; + cws = true; + } + else if (cws) + { + if (c == ' ') + ++wc; + else + cws = false; + } + } + + // Flush the last line. + // + if (b != i) + { + if (b != 0) + os << endl + << line_start << string (indent + wi, ' ') << np; + + string s (d, b, i - b); + + if (escape != 0) + s = escape (s); + + os << s << line_end; + } +} + +namespace +{ + struct doc: traversal::doc, context + { + doc (context& c, class_doc_type cd): context (c), cd_ (cd) {} + + virtual void + traverse (type& ds) + { + if (ds.name ().compare (0, 3, "doc") != 0) // Ignore doc variables. + return; + + // n = 1 - common doc string + // n = 2 - arg string, common doc string + // n > 2 - arg string, short string, long string + // + size_t n (ds.size ()); + const string& d ( + n == 1 + ? (cd_ == cd_short ? first_sentence (ds[0]) : ds[0]) + : (n == 2 + ? (cd_ == cd_short ? first_sentence (ds[1]) : ds[1]) + : ds[cd_ == cd_short ? 1 : 2])); + + std::set<string> arg_set; + if (n > 1 && options.ansi_color ()) + translate_arg (ds[0], arg_set); + + string s (format (ds.scope (), translate (d, arg_set), true)); + + if (s.empty ()) + return; + + txt_wrap_lines (os, s); + os << endl; //@@ ?? + } + + private: + class_doc_type cd_; + }; + + struct option: traversal::option, context + { + option (context& c, class_doc_type) : context (c)/*, cd_ (cd)*/ {} + + virtual void + traverse (type& o) + { + using semantics::names; + + semantics::doc_strings const& doc (o.doc ()); + + if (options.suppress_undocumented () && doc.empty ()) + return; + + //@@ TODO + } + + private: + // class_doc_type cd_; + }; + + // + // + struct class_: traversal::class_, context + { + class_ (context& c): context (c), base_ (false) + { + *this >> inherits_ >> *this; + } + + virtual void + traverse (type& c) + { + class_doc_type cd (class_doc (c)); + + if (cd == cd_exclude || (base_ && cd == cd_exclude_base)) + return; + + if (!options.exclude_base () && !options.include_base_last ()) + { + bool ob (base_); + base_ = true; + inherits (c); + base_ = ob; + } + + doc dc (*this, cd); + option op (*this, cd); + traversal::names n; + n >> dc; + n >> op; + names (c, n); + + if (!options.exclude_base () && options.include_base_last ()) + { + bool ob (base_); + base_ = true; + inherits (c); + base_ = ob; + } + } + + private: + bool base_; + traversal::inherits inherits_; + }; +} + +void +generate_txt (context& ctx) +{ + traversal::cli_unit unit; + traversal::names unit_names; + traversal::namespace_ ns; + doc dc (ctx, cd_default); + class_ cl (ctx); + unit >> unit_names; + unit_names >> ns; + unit_names >> dc; + unit_names >> cl; + + traversal::names ns_names; + ns >> ns_names; + ns_names >> ns; + ns_names >> dc; + ns_names >> cl; + + if (ctx.options.class_ ().empty ()) + unit.dispatch (ctx.unit); + else + { + for (vector<string>::const_iterator i (ctx.options.class_ ().begin ()); + i != ctx.options.class_ ().end (); ++i) + { + string n (*i); + + // Strip leading :: if present. + // + if (n.size () > 2 && n[0] == ':' && n[1] == ':') + n = string (n, 2, string::npos); + + if (semantics::class_* c = ctx.unit.lookup<semantics::class_> ("", n)) + cl.traverse (*c); + else + { + cerr << "error: class '" << *i << "' not found" << endl; + throw generation_failed (); + } + } + } +} diff --git a/cli/cli/txt.hxx b/cli/cli/txt.hxx new file mode 100644 index 0000000..cde31c9 --- /dev/null +++ b/cli/cli/txt.hxx @@ -0,0 +1,46 @@ +// file : cli/txt.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_TXT_HXX +#define CLI_TXT_HXX + +#include <iosfwd> // ostream +#include <string> +#include <cstddef> // size_t + +#include <cli/context.hxx> + +void +generate_txt (context&); + +// Return the number of "text characters", ignoring any escape sequences +// (e.g., ANSI color). +// +std::size_t +txt_size (const std::string&, + std::size_t p = 0, + std::size_t n = std::string::npos); + +// This function assumes that the initial opening part has already been +// written with the 'first' argument being the number of characters already +// written in the first line (e.g., an option name). The 'indent' argument +// specified how many spaces to indent each line. The 'escape' argument is +// the optional escape function (e.g., for the string literal output). The +// line_{start, end, blank} arguments specify optional extra text for the +// start/end of the non-empty line as well as for the blank (empty) line. +// +// Note that the funtion modifies the passed string in order to translate +// notes. +// +void +txt_wrap_lines (std::ostream& os, + std::string&, + std::size_t indent = 0, + std::size_t first = 0, + const char* line_start = "", + const char* line_end = "", + const char* line_blank = "", + std::string (*escape) (std::string const&) = 0); + +#endif // CLI_TXT_HXX diff --git a/cli/cli/version.hxx.in b/cli/cli/version.hxx.in new file mode 100644 index 0000000..90b649a --- /dev/null +++ b/cli/cli/version.hxx.in @@ -0,0 +1,45 @@ +// file : cli/version.hxx.in +// author : Boris Kolpackov <boris@codesynthesis.com> +// license : MIT; see accompanying LICENSE file + +#ifndef CLI_VERSION // Note: using the version macro itself. + +// The numeric version format is AAAAABBBBBCCCCCDDDE where: +// +// AAAAA - major version number +// BBBBB - minor version number +// CCCCC - bugfix version number +// DDD - alpha / beta (DDD + 500) version number +// E - final (0) / snapshot (1) +// +// When DDDE is not 0, 1 is subtracted from AAAAABBBBBCCCCC. For example: +// +// Version AAAAABBBBBCCCCCDDDE +// +// 0.1.0 0000000001000000000 +// 0.1.2 0000000001000020000 +// 1.2.3 0000100002000030000 +// 2.2.0-a.1 0000200001999990010 +// 3.0.0-b.2 0000299999999995020 +// 2.2.0-a.1.z 0000200001999990011 +// +#define CLI_VERSION $cli.version.project_number$ULL +#define CLI_VERSION_STR "$cli.version.project$" +#define CLI_VERSION_ID "$cli.version.project_id$" + +#define CLI_VERSION_MAJOR $cli.version.major$ +#define CLI_VERSION_MINOR $cli.version.minor$ +#define CLI_VERSION_PATCH $cli.version.patch$ + +#define CLI_PRE_RELEASE $cli.version.pre_release$ + +#define CLI_SNAPSHOT $cli.version.snapshot_sn$ULL +#define CLI_SNAPSHOT_ID "$cli.version.snapshot_id$" + +#define CLI_CHECKSUM "$cli.version$" + +#include <cutl/version.hxx> + +$libcutl.check(LIBCUTL_VERSION, LIBCUTL_SNAPSHOT)$ + +#endif // CLI_VERSION |