summaryrefslogtreecommitdiff
path: root/cli/cli/generator.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'cli/cli/generator.cxx')
-rw-r--r--cli/cli/generator.cxx584
1 files changed, 584 insertions, 0 deletions
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 ();
+ }
+}