// file : cli/generator.cxx // author : Boris Kolpackov // license : MIT; see accompanying LICENSE file #include // toupper, is{alpha,upper,lower} #include #include #include // move() #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 const& text, string const& file, vector* pdeps) { for (vector::const_iterator i (text.begin ()); i != text.end (); ++i) { append (ctx, *i); } if (!file.empty ()) { ifstream ifs; open (ifs, file); path p (file); path d (p.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); if (pdeps != nullptr) pdeps->push_back (move (p.normalize ())); } } } generator:: generator () { } void generator:: generate (options& ops, semantics::cli_unit&& unit, vector&& deps, 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 (); } } bool gen_dep (ops.generate_dep ()); vector* pdeps (gen_dep ? &deps : nullptr); vector depts; // Dependents. fs::auto_removes auto_rm; // gen_dep // // Make sure that we remove the potentially outdated dependency file if we // fail to generate any source/documentation file. // // Note that we will write the dependency file content later, when all the // dependents and dependencies are determined. // ofstream dep; if (gen_dep) { path dep_path; if (ops.dep_file ().empty ()) { dep_path = path (pfx + base + sfx + ops.dep_suffix ()); if (!ops.output_dir ().empty ()) dep_path = path (ops.output_dir ()) / dep_path; } else dep_path = path (ops.dep_file ()); dep.open (dep_path.string ().c_str (), ios_base::out); if (!dep.is_open ()) { cerr << "error: unable to open '" << dep_path << "' in write mode" << endl; throw failed (); } auto_rm.add (dep_path); } // 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 ()) { 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); if (gen_dep) depts.push_back (move (hxx_path.normalize ())); // // 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); if (gen_dep) depts.push_back (move (ixx_path.normalize ())); } // // 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); if (gen_dep) depts.push_back (move (cxx_path.normalize ())); // Print headers. // hxx << cxx_header; if (inl) ixx << cxx_header; cxx << cxx_header; typedef compiler::ostream_filter 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 (), pdeps); 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 (), pdeps); 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 (), pdeps); 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 (), pdeps); 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 (), pdeps); 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 (), pdeps); 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); if (gen_dep) depts.push_back (move (man_path.normalize ())); } // The explicit cast helps VC++ 8.0 overcome its issues. // ostream& os (ops.stdout_ () ? cout : static_cast (man)); context ctx (os, context::ot_man, unit, ops); for (bool first (true); first || ctx.toc; first = false) { append (ctx, ops.man_prologue (), ops.man_prologue_file (), pdeps); generate_man (ctx); append (ctx, ops.man_epilogue (), ops.man_epilogue_file (), pdeps); 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); if (gen_dep) depts.push_back (move (html_path.normalize ())); } // The explicit cast helps VC++ 8.0 overcome its issues. // ostream& os (ops.stdout_ () ? cout : static_cast (html)); context ctx (os, context::ot_html, unit, ops); for (bool first (true); first || ctx.toc; first = false) { append (ctx, ops.html_prologue (), ops.html_prologue_file (), pdeps); generate_html (ctx); append (ctx, ops.html_epilogue (), ops.html_epilogue_file (), pdeps); 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); if (gen_dep) depts.push_back (move (txt_path.normalize ())); } // The explicit cast helps VC++ 8.0 overcome its issues. // ostream& os (ops.stdout_ () ? cout : static_cast (txt)); context ctx (os, context::ot_plain, unit, ops); for (bool first (true); first || ctx.toc; first = false) { append (ctx, ops.txt_prologue (), ops.txt_prologue_file (), pdeps); generate_txt (ctx); append (ctx, ops.txt_epilogue (), ops.txt_epilogue_file (), pdeps); if (ctx.toc) { assert (first); // Second run should end in non-TOC mode. ctx.toc++; // TOC phase after restart. } } ctx.verify_id_ref (); } // gen_dep // if (gen_dep) { // Write the specified path to the dependencies file stream, escaping // colons and backslashes. // auto write = [&dep] (path const& p) { for (char c: p.string ()) { if (c == ':' || c == '\\') dep << '\\'; dep << c; } }; // Note that we don't add the dependency file as a dependent, but in the // future may invent some option which triggers that. // bool first (true); for (const auto& p: depts) { if (!first) dep << " \\" << endl; else first = false; write (p); } dep << ':'; for (const auto& p: deps) { dep << " \\" << endl << " "; write (p); } dep << endl; } 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 (); } }