//@copyright_begin // ================================================================ // Copyright Notice // Copyright (C) 1998-2004 by Joe Linoff // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL JOE LINOFF BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Comments and suggestions are always welcome. // Please report bugs to http://ccdoc.sourceforge.net/ccdoc // ================================================================ //@copyright_end #include "phase3.h" #include "phase3_html.h" #include // ================================================================ // This variable allows the header version // to be queried at runtime. // ================================================================ namespace { char ccdoc_rcsid[] = "$Id: phase3.cc,v 1.15 2004/09/30 04:16:07 jlinoff Exp $"; } // ================================================================ // Local methods. // ================================================================ namespace { // ================================================================ // Move the children from one parent to the other. // ================================================================ void move_namespace_children( ccdoc::statement::base* from, ccdoc::statement::base* to) { // Move over all of the children so that later // code references can find them. ccdoc::statement::base::stmts_t& children = from->get_children(); if( children.size() ) { // Make a copy to avoid corruption problems. vector vec; copy(children.begin(), children.end(), back_inserter >(vec)); ccdoc::statement::base::stmts_t::iterator citr; for(citr = vec.begin();citr!=vec.end();++citr) { ccdoc::statement::base* child = *citr; child->set_parent(to); } } } // ================================================================ // Populate the comment statement. // ================================================================ void create_namespace_comment(string& real_id, ccdoc::statement::base* comment_stmt, ccdoc::switches& sw) { ccdoc::statement::base* begin_stmt = comment_stmt->get_comment(); if( begin_stmt->get_next() && begin_stmt->get_next()->get_next() == 0 ) { // There is only one related namespace with a comment. ccdoc::statement::base* comment = begin_stmt->get_next()->get_comment(); if( comment ) { // Copy the comment over verbatim. const vector& vec = comment->get_tokens(); vector::const_iterator itr = vec.begin(); for(;itr!=vec.end();++itr) { comment_stmt->add_token(*itr); } // Break the chain because there is no need // to access the "other" comment. begin_stmt->set_next(0); return; } } vector txt; comment_stmt->add_token("@{"); comment_stmt->add_token("@file"); comment_stmt->add_token("2"); comment_stmt->add_token("generated"); comment_stmt->add_token("0"); comment_stmt->add_token("@type"); comment_stmt->add_token("1"); comment_stmt->add_token("@prefix"); comment_stmt->add_token("@short_desc"); comment_stmt->add_token("1"); if( real_id == "-anonymous-" ) comment_stmt->add_token("Anonymous namespace."); else { string short_desc; short_desc = "Namespace "; if( real_id.size() > sw.rptmlcei() ) { string short_id; short_id = '"'; short_id += real_id.substr(0,sw.rptmlcei()); short_id += "..\""; short_desc += short_id; } else { short_desc += real_id; } short_desc += "."; comment_stmt->add_token(short_desc.c_str()); } comment_stmt->add_token("@long_desc"); // Create the long description. int num_reported_files = 0; ccdoc::statement::base* stmt = begin_stmt->get_next(); for(;stmt; stmt=stmt->get_next()) { if( !sw.rptcfuns() && !stmt->get_comment() ) { // Don't report namespaces with empty comments. continue; } ++num_reported_files; } if( num_reported_files ) { txt.push_back("It is generated from \"namespace "); if( real_id[0] != '-' ) txt.push_back(real_id.c_str()); txt.push_back(" { .. }\" declarations in the "); txt.push_back("following source files:
    "); // Write out the summary info. stmt = begin_stmt->get_next(); for(;stmt; stmt=stmt->get_next()) { if( !sw.rptcfuns() && !stmt->get_comment() ) { // Don't report namespaces with empty comments. continue; } const char* pid = stmt->get_id(); for(;*pid!='+' && *pid;++pid); if( *pid ) ++pid; for(;*pid!='+' && *pid;++pid); if( *pid ) ++pid; txt.push_back("
  • "); txt.push_back(pid); txt.push_back("
  • "); } txt.push_back("
"); } else { // There weren't any files with documentation. // Tell the user. txt.push_back("It is generated from \"namespace "); if( real_id[0] != '-' ) txt.push_back(real_id.c_str()); txt.push_back(" { .. }\" declarations. "); txt.push_back("

None of the namespaces were documented "); txt.push_back("so the files won't show up unless you use the "); txt.push_back("-rptcfuns switch during phase 3 processing."); } // Write out the long description. char num[32]; sprintf(num,"%d",txt.size()); comment_stmt->add_token(num); vector::iterator sitr = txt.begin(); for(;sitr!=txt.end();++sitr) { comment_stmt->add_token((*sitr).c_str()); } comment_stmt->add_token("@params"); // append "s" comment_stmt->add_token("0"); comment_stmt->add_token("@returns"); comment_stmt->add_token("0"); comment_stmt->add_token("@exceptions"); // append "s" comment_stmt->add_token("0"); comment_stmt->add_token("@deprecated"); comment_stmt->add_token("0"); comment_stmt->add_token("@authors"); comment_stmt->add_token("1"); comment_stmt->add_token("ccdoc"); comment_stmt->add_token("@version"); comment_stmt->add_token("0"); comment_stmt->add_token("@sees"); // append "s" comment_stmt->add_token("0"); comment_stmt->add_token("@since"); comment_stmt->add_token("0"); comment_stmt->add_token("@source"); comment_stmt->add_token("0"); comment_stmt->add_token("@pkg"); comment_stmt->add_token("0"); comment_stmt->add_token("@pkgdoc"); comment_stmt->add_token("0"); comment_stmt->add_token("@todo"); comment_stmt->add_token("0"); comment_stmt->add_token("@}"); } // ================================================================ // Move namespace children. // ================================================================ void move_namespace_children(ccdoc::statement::base* begin_stmt, vector& related_nsps) { vector::iterator itr; itr = related_nsps.begin(); for(;itr!=related_nsps.end();++itr) { ccdoc::statement::base* stmt = *itr; move_namespace_children( stmt, begin_stmt ); } } // ================================================================ // Chain related namespaces. // ================================================================ void chain_related_namespaces(ccdoc::statement::base* begin_stmt, vector& chained_nsps) { ccdoc::statement::base* chain = begin_stmt; vector::iterator itr; itr = chained_nsps.begin(); for(;itr!=chained_nsps.end();++itr) { ccdoc::statement::base* stmt = *itr; chain->set_next(stmt); chain = stmt; } chain->set_next(0); } // ================================================================ // Erase generated namespaces. // ================================================================ void erase_generated_namespaces(ccdoc::database& db) { vector stmts; db.load(stmts,ccdoc::statement::base::STMT_NAMESPACE_BEGIN); db.load(stmts,ccdoc::statement::base::STMT_NAMESPACE_END); vector::iterator itr; // Remove the internally generated namespaces. // This guarantees that things will work when // an existing db is read. if( !stmts.empty() ) { vector erased_stmts; for(itr = stmts.begin();itr!=stmts.end();++itr) { ccdoc::statement::base* stmt = *itr; const char* pid = stmt->get_id(); if( *pid != '+' ) { // This is an internally generated namespace. erased_stmts.push_back(stmt); if( stmt->get_comment() ) { // Make sure that the comment is erased as well. erased_stmts.push_back( stmt->get_comment() ); } } } if( erased_stmts.size() ) { vector::reverse_iterator ritr; for(ritr=erased_stmts.rbegin();ritr!=erased_stmts.rend();++ritr) { delete *ritr; } } } } // ================================================================ // Issue 0133 // Combine all of the related namespaces. // ================================================================ void combine_related_namespaces(ccdoc::database& db, ccdoc::switches& sw) { vector stmts; db.load(stmts,ccdoc::statement::base::STMT_NAMESPACE_BEGIN); db.load(stmts,ccdoc::statement::base::STMT_NAMESPACE_END); vector::iterator itr; erase_generated_namespaces(db); // Walk through and re-create the generated namespace // names. stmts.clear(); db.load(stmts,ccdoc::statement::base::STMT_NAMESPACE_BEGIN); if( !stmts.empty() ) { set matches; for(itr = stmts.begin();itr!=stmts.end();++itr) { ccdoc::statement::base* stmt = *itr; const char* pid = stmt->get_id(); if( *pid == '+' ) { // This is the special case form of the namespace // that is tagged in the parser to indicate that // the file name has been attached. // See: ccdoc::phase1::parser::parse_scoping_stmt_beg // Format: ++: // Extract the "real" namespace name ("+""+"). string real_id; for(++pid;*pid!='+';++pid) real_id += *pid; // Get the parent. ccdoc::statement::base* parent = stmt->get_parent(); ccdoc::statement::base* file = stmt; // Has this real_id has already been processed // for this parent? // Remember that namespaces with the same name // can exist in different parents. string key; parent->get_hier_id(key); key += " "; key += real_id; if( matches.find(key) != matches.end() ) { continue; } matches.insert(key); // First figure out exactly how many // related namespaces there so that // I can determine whether a new comment // record is needed. vector related_nsps; vector::iterator nitr = itr; related_nsps.push_back( stmt ); for(++nitr;nitr!=stmts.end();++nitr) { ccdoc::statement::base* stmt1 = *nitr; if( stmt1->get_parent() == parent ) { pid = stmt1->get_id(); if( *pid == '+' ) { const char* pr = real_id.c_str(); for(++pid;*pid == *pr;++pid,++pr) ; if( *pid == '+' && *pr == 0 ) { // This is a match save it. related_nsps.push_back( stmt1 ); } } } } // Now define the namespaces that are chained together // for documentation purposes. This is different than // the related namespaces because the related namespaces // define the entities that are moved into the generated // namespace whereas the chained namespaces are only used // for documentation. vector chained_nsps; if( !related_nsps.empty() ) { vector::iterator nitr; nitr = related_nsps.begin(); for(;nitr!=related_nsps.end();++nitr ) { ccdoc::statement::base* nsp = *nitr; if( !sw.rptcfuns() && !nsp->get_comment() ) { // Issue 0155. // Don't report namespaces with empty comments. continue; } chained_nsps.push_back( nsp ); } // If none of the namespaces have comments, // copy over first one. if( chained_nsps.empty() ) { nitr = related_nsps.begin(); chained_nsps.push_back(*nitr); } } // Create a dummy comment for use with the generated namespace // if there are multiple related namespaces or if there // is one related namespace that is associated with // a comment. ccdoc::statement::base* comment_stmt = 0; if( chained_nsps.size() > 1 || ( chained_nsps.size() == 1 && !chained_nsps.back()->get_comment() ) ) { string comment_id; char num[32]; sprintf(num,"%d",stmt->get_lineno()); db.get_next_comment_id(comment_id); comment_stmt = new ccdoc::statement::base; comment_stmt->set_type( ccdoc::statement::base::STMT_COMMENT_PREFIX ); comment_stmt->set_access( ccdoc::statement::base::STMT_PUBLIC ); comment_stmt->set_id( comment_id ); comment_stmt->set_lineno( file->get_lineno() ); comment_stmt->set_file( file->get_file() ); parent->add_child(comment_stmt); } // Create a new namespace with the "real" name // and attach the members from the reference // namespaces. ccdoc::statement::base* begin_stmt = 0; begin_stmt = new ccdoc::statement::base; begin_stmt->set_id( real_id ); begin_stmt->set_type( ccdoc::statement::base::STMT_NAMESPACE_BEGIN ); begin_stmt->set_access( ccdoc::statement::base::STMT_PUBLIC ); begin_stmt->set_file( file->get_file() ); begin_stmt->set_lineno( file->get_lineno() ); parent->add_child(begin_stmt); // Create the end statement. ccdoc::statement::base* end_stmt = 0; end_stmt = new ccdoc::statement::base; end_stmt->set_id( real_id ); end_stmt->set_type( ccdoc::statement::base::STMT_NAMESPACE_END ); end_stmt->set_access( ccdoc::statement::base::STMT_PUBLIC ); end_stmt->set_file( file->get_file() ); end_stmt->set_lineno( file->get_lineno() ); parent->add_child(end_stmt); // Move the namespace children to the generated namespace. move_namespace_children(begin_stmt,related_nsps); if( comment_stmt ) { // Chain together the associated comments. chain_related_namespaces(begin_stmt,chained_nsps); // Associate the comment with the statement. begin_stmt->set_comment(comment_stmt); comment_stmt->set_comment(begin_stmt); // Populate the comment. create_namespace_comment(real_id,comment_stmt,sw); // Clean up the begin/end stmt stuff. if( chained_nsps.size() > 1 ) { begin_stmt->set_file( "generated" ); begin_stmt->set_lineno( 0 ); end_stmt->set_file( "generated" ); end_stmt->set_lineno( 0 ); } } else { if( chained_nsps.size() == 1 && chained_nsps.back()->get_comment() ) { // Associate the umbrella comment with the new // namespace. comment_stmt = chained_nsps.back()->get_comment(); begin_stmt->set_comment(comment_stmt); comment_stmt->set_comment(begin_stmt); } } } } } } // ================================================================ // Issue 0144: // Set the static and template flags for statements. // This cannot be done earlier because they are not // stored in the database. // In the next version of ccdoc, it would be convient to // to store them their and do this in phase1. // ================================================================ void set_stmt_flags(ccdoc::database& db, ccdoc::switches& sw) { vector stmts; vector::iterator itr; // ================================================ // Check for class scoped methods and attributes // by looking for the static keyword. // It is unfortunate that the static keyword // denotes both class scoping and module level // scoping. Fortunately, namespaces will eliminate // the latter use over time. // ================================================ db.load(stmts,ccdoc::statement::base::STMT_METHOD); db.load(stmts,ccdoc::statement::base::STMT_ATTRIBUTE); db.load(stmts,ccdoc::statement::base::STMT_ATTRIBUTE_FUNCTION); for(itr=stmts.begin();itr!=stmts.end();++itr) { ccdoc::statement::base* stmt = *itr; const ccdoc::statement::base::cstrs_t tokens = stmt->get_tokens(); ccdoc::statement::base::cstrs_citr_t itr1 = tokens.begin(); for(;itr1!=tokens.end();++itr1) { string tmp = *itr1; if( tmp == "static" ) { stmt->set_static(true); break; } } } // ================================================ // Check for templates. // ================================================ stmts.clear(); db.load(stmts,ccdoc::statement::base::STMT_FUNCTION); db.load(stmts,ccdoc::statement::base::STMT_CLASS_BEGIN); for(itr=stmts.begin();itr!=stmts.end();++itr) { ccdoc::statement::base* stmt = *itr; const ccdoc::statement::base::cstrs_t tokens = stmt->get_tokens(); ccdoc::statement::base::cstrs_citr_t itr1 = tokens.begin(); for(;itr1!=tokens.end();++itr1) { string tmp = *itr1; if( tmp == "template" ) { stmt->set_template(true); break; } } } } // ================================================================ // delete_duplicate_macros // ================================================================ void delete_duplicate_macros(ccdoc::database& db, ccdoc::switches& sw) { ccdoc::statement::base::stmts_t stmts; db.load_top(stmts,ccdoc::statement::base::STMT_MACRODEF_0_0); db.load_top(stmts,ccdoc::statement::base::STMT_MACRODEF_0_1); db.load_top(stmts,ccdoc::statement::base::STMT_MACRODEF_0_N); db.load_top(stmts,ccdoc::statement::base::STMT_MACRODEF_N_N); ccdoc::statement::base::stmts_itr_t itr = stmts.begin(); set names; string key; for(;itr!=stmts.end();++itr) { ccdoc::statement::base* stmt = *itr; // Remove rptmac1() type macros. if( sw.rptmac1() && stmt->is_rptmac1_id() ) { delete stmt; continue; } key = stmt->get_id(); set::iterator itr1 = names.find(key); if( itr1 != names.end() ) { // Found a duplicate, schedule // it for deletion. delete stmt; ccdoc::s_log.warning() << "Multiply defined macro '" << key << "' ignored at line " << stmt->get_lineno() << " in " << stmt->get_file() << ".\n" << ccdoc::s_log.enable(); } else { names.insert(key); } } } } // ================================================================ // Run // ================================================================ bool ccdoc::phase3::run(switches& sw,database& db) { if(sw.verbose()) { s_log << "phase3: begins\n"; } bool debug = false; if( ::getenv("CCDOC_PHASE3_DEBUG") ) { debug = true; s_log << "CCDOC_PHASE3_DEBUG: " << "================================================\n"; s_log << "CCDOC_PHASE3_DEBUG: file: " << sw.db() << "\n"; db.debug_dump("CCDOC_PHASE3_DEBUG: "); } // Issue 0152: // Remove duplicate macros and rptmac1 type macros. // This is done here for performance reasons. // Originally I did this in phase 1 but that // led to O(N^2) behavior for large systems. // This must be done before load_path_map(). // It would be interesting delete_duplicate_macros(db,sw); // This must be done here because I chose not to change the db // format for r33 (or later). set_stmt_flags(db,sw); // This must be done here because I chose not to change the db // format for r27 (or later) which means that the chain is not // persistent. combine_related_namespaces(db,sw); html h(sw,db); h.set_debug(debug); bool status = h.run(); if(sw.verbose()) { s_log << "phase3: ends\n"; } return status; }