/******************************************************************************* * Simplified Wrapper and Interface Generator (SWIG) * * Author : David Beazley * * Department of Computer Science * University of Chicago * 1100 E 58th Street * Chicago, IL 60637 * beazley@cs.uchicago.edu * * Please read the file LICENSE for the copyright and terms by which SWIG * can be used and distributed. *******************************************************************************/ #include "internal.h" /******************************************************************************* * $Header$ * * File : comment.cxx * * This is a semi-magical module for associating C/C++ comments with * documentation entries. While this sounds like it might be easy, * there are a number of subtle problems getting things to associate * correctly. * * Here's the general idea : * * 1. The parser and scanner feed both C comments and documentation * entries to this class. These may show up in really bizarre * orders (not necessarily the order seen in an interface file). * * 2. We maintain separate lists of comments and documentation * entries. * * 3. Periodically, we go through the list of documentation entries * and see if we can associate any comments. * * 4. Upon completion of parsing, it's critical that we cleanup * the lists using the cleanup() method. * *******************************************************************************/ // ----------------------------------------------------------------------------- // struct Comment // // Structure used to maintain a linked list of comments for later use. // ----------------------------------------------------------------------------- class Comment { public: String *text; // Text of the comment int first_line; // First line of the comment int last_line; // Last line of the comment int column; // First column of comment char *file; // Name of the file that it was in Comment *next; // Next comment (when in a linked list) Comment *prev; // Previous comment static Comment *comment_list; // List of all comments Comment(char *t, int line, int col, char *f); ~Comment(); static Comment *find(DocEntry *de, CommentHandler *ch); void attach(DocEntry *de, CommentHandler *ch); }; // ----------------------------------------------------------------------- // Create a new comment. Automatically puts it on the linked list // ----------------------------------------------------------------------- Comment::Comment(char *t, int line, int col, char *f) { int nlines = 0; char *c; text = new String(t); c = t; while (*c) { if (*c == '\n') nlines++; c++; } first_line = line; column = col; last_line = line + nlines - 1; file = copy_string(f); if (comment_list) { comment_list->prev = this; } next = comment_list; comment_list = this; prev = 0; } // ----------------------------------------------------------------------- // Destroy a comment // ----------------------------------------------------------------------- Comment::~Comment() { delete text; delete file; // Remove from linked list (if applicable) if (prev) { prev->next = next; } if (next) { next->prev = prev; } if (this == comment_list) comment_list = next; } // ----------------------------------------------------------------------- // find(DocEntry *de, CommentHandler *ch) // // This function tries to a find a comment matching the search criteria // of a given comment handler and documentation entry. // ----------------------------------------------------------------------- Comment *Comment::find(DocEntry *de, CommentHandler *ch) { Comment *c; c = comment_list; // Start walking down our list of stored comments while (c) { // printf("Searching %x : %s\n", c, c->text->get()); if (strcmp(de->file,c->file) == 0) { // At least comment is in the right file. Now check line numbers if (ch->location == BEFORE) { // Check to see if the last line of the comment is close // enough to our declaration. if ((c->last_line <= de->line_number) && ((de->line_number - c->last_line) <= ch->skip_lines)) { return c; } } else { // AFTER mode // Check to see if the first line of the comment is close // enough to our declaration. if ((c->first_line >= de->end_line) && ((c->first_line - de->end_line) <= ch->skip_lines)) { return c; } } // Check to see if the line numbers are too small. Comments // are processed in order so there's no sense in checking // all entries. if (c->last_line < de->line_number) return 0; } c = c->next; } return 0; } // ----------------------------------------------------------------------- // void attach(DocEntry *de, CommentHandler *ch) // // This function attachs a comment to a documentation entry and applies // all of the style information in the comment handler. // ----------------------------------------------------------------------- void Comment::attach(DocEntry *de, CommentHandler *ch) { int nlines = 0; char **split = 0; char *c; int i,lnum,el; if (!de) return; // If we're ignoring comments, forget it if (ch->ignore) { return; } // If the comment is formatted, no style processing is applied if (de->format) { de->text << *text; return; } // Untabify the comment if (ch->untabify) text->untabify(); // Count how many lines we have c = text->get(); while (*c) { if (*c == '\n') nlines++; c++; } if (nlines == 0) return; // Tokenize the documentation string into lines split = new char*[nlines+1]; c = text->get(); i = 0; split[i] = c; while (*c) { if (*c == '\n') { *(c++) = 0; split[++i] = c; } else c++; } lnum = 0; // Now process the chop_top and chop_bottom values // if nlines < (chop_top + chop_bottom), then we do nothing if (nlines > (ch->chop_top + ch->chop_bottom)) { lnum += ch->chop_top; el = nlines-ch->chop_bottom; } else { el = nlines; } // Now process in-between lines while (lnum < el) { /* Chop line */ if (split[lnum]) { if (strlen(split[lnum]) > (unsigned) (ch->chop_left+ch->chop_right)) { if (ch->chop_right > 0) split[lnum][strlen(split[lnum]) - ch->chop_right] = 0; de->text << &split[lnum][ch->chop_left]; } } lnum++; de->text << "\n"; } // printf("*** ATTACHING %s : %s\n", de->usage.get(), de->text.get()); delete split; } CommentHandler *comment_handler = 0; Comment *Comment::comment_list = 0; // ------------------------------------------------------------------------ // struct DocEntryList // // This structure manages a linked list of documentation entries that // haven't had comments attached to them yet. // // As a general rule, this list tends to remain rather short. // ------------------------------------------------------------------------ struct DocEntryList { DocEntry *de; CommentHandler *ch; DocEntryList *next; DocEntryList *prev; static DocEntryList *doc_list; // ----------------------------------------------------------------------- // Create a new list entry // ----------------------------------------------------------------------- DocEntryList(DocEntry *d, CommentHandler *c) { de = d; ch = c; next = doc_list; prev = 0; if (doc_list) doc_list->prev = this; doc_list = this; // Only allow a few doc entries to survive if (this->next) { if (this->next->next) { delete this->next->next; } } } // ----------------------------------------------------------------------- // Destroy a list entry // ----------------------------------------------------------------------- ~DocEntryList() { if (prev) { prev->next = next; } if (next) { next->prev = prev; } if (this == doc_list) doc_list = next; }; // ----------------------------------------------------------------------- // static check() // // Checks the list of documentation entries to see if any can be associated. // ----------------------------------------------------------------------- static void check() { DocEntryList *dl, *dl_temp; Comment *cmt; // printf ("Checking\n"); dl = doc_list; while (dl) { cmt = Comment::find(dl->de,dl->ch); if (cmt) { // Okay, we found a matching comment. Attach it to this // documentation entry. cmt->attach(dl->de,dl->ch); // Destroy the comment and doc list entry delete cmt; // Declarations are always coming in order so we're going // to blow away all of them past this point dl_temp = dl->next; delete dl; dl = dl_temp; } else { dl = dl->next; } } } }; DocEntryList *DocEntryList::doc_list = 0; // ----------------------------------------------------------------------------- // CommentHandler::CommentHandler() // // Constructor. Creates a new comment handler. Sets up some default values // for comment handling. // // Inputs : None // // Output : New CommentHandler object. // // Side Effects : Sets default comment handling parameters. // ----------------------------------------------------------------------------- CommentHandler::CommentHandler() { skip_lines = 1; location = AFTER; chop_top = 0; chop_bottom = 0; chop_left = 3; chop_right = 0; untabify = 1; ignore = 0; } // ----------------------------------------------------------------------------- // CommentHandler::CommentHandler(CommentHandler *c) // // Constructor. Creates a new comment handler, but copies attributes from // another handler. // // Inputs : // c = A different comment handler. // // Output : A new CommentHandler object. // // Side Effects : None // ----------------------------------------------------------------------------- CommentHandler::CommentHandler(CommentHandler *c) { skip_lines = c->skip_lines; location = c->location; chop_top = c->chop_top; chop_bottom = c->chop_bottom; chop_left = c->chop_left; chop_right = c->chop_right; untabify = c->untabify; ignore = c->ignore; } // ----------------------------------------------------------------------------- // CommentHandler::~CommentHandler() // // Destructor. Destroys a comment handler. Does nothing interesting at the // moment. // // Inputs : None // // Output : None // // Side Effects : None // ----------------------------------------------------------------------------- CommentHandler::~CommentHandler() { } // ----------------------------------------------------------------------------- // void CommentHandler::add_comment(char *text, int line_num, int col, char *file) // // This function takes a character string as comment text and appends // it to the current comment string (which is held in Comment::comment_list) // // 1. If two comments appear in successive lines, they are // concatenated. This is to handle C++ style comments like the // one surrounding this text. // // 2. If a new comment appears, we simply create a new one // // Inputs : // text = Text of the comment // line_num = Starting line number of the comment // col = Starting column of the comment // file = File in which the comment was located. // // Output : None // // Side Effects : // Saves the comment in an internal linked list. // If multiple comments appear in succession, some may end up // in our comment list permanently (ie. never attached to any // particular declaration). // ----------------------------------------------------------------------------- void CommentHandler::add_comment(char *text, int line_num, int col, char *file) { char *c; int nlines = 0; Comment *cmt; // printf("line_num = %d, %s\n", line_num,text); // Count up how many lines are in this comment c = text; while (*c) { if (*c == '\n') nlines++; c++; } // Check to see if this comment is in a successive line to the last one cmt = Comment::comment_list; if (cmt) { // Check for column alignment if ((cmt->column == col) && (line_num == (cmt->last_line + 1)) && (nlines <= 1)) { *(cmt->text) << text; cmt->last_line = line_num + nlines - 1; } else { // This is a new comment, add it to our list cmt = new Comment(text,line_num,col,file); } } else { cmt = new Comment(text,line_num,col,file); } } // ----------------------------------------------------------------------------- // void CommentHanlder::set_entry(DocEntry *d) // // This grabs a DocEntry and hangs onto it. // // We will place the doc entry into our documentation list and then // check it to see if any comments are sitting around. // // Inputs : d = Documentation Entry // // Output : None // // Side Effects : // May attach comments to the documentation entry. In this case, // comments and DocEntries may be removed from internal lists. // ----------------------------------------------------------------------------- void CommentHandler::set_entry(DocEntry *d) { // printf("Set entry : file: %s, line %d, %s\n", d->file, d->line_number, d->usage.get()); // Create a new list entry and save it new DocEntryList(d,this); // Check all of the documentation entries to see if they can be placed DocEntryList::check(); } // ----------------------------------------------------------------------------- // static void CommentHandler::cleanup() // // Checks all documentation entries and sees if there are any comments available. // If so, they are attached. This function is usually only called upon completion // of parsing. // // Inputs : None // // Output : None // // Side Effects : // Removes documentation entries and comments from internal lists. // // ----------------------------------------------------------------------------- void CommentHandler::cleanup() { int nc, nd; Comment *c; DocEntryList *d; DocEntryList::check(); // Figure out how bad we're doing on memory nc = 0; nd = 0; c = Comment::comment_list; while (c) { nc++; c = c->next; } d = DocEntryList::doc_list; while(d) { nd++; d = d->next; } if (Verbose) { printf("%d unprocessed comments, %d unprocessed doc entries.\n",nc,nd); } } // ----------------------------------------------------------------------------- // void CommentHandler::style(char *name, char *value) // // Processes comment handling style parameters. The following parameters // are available : // // after - Comments appear after a declaration // before - Comments appear before a declaration // skip - Number of blank lines between comment and decl. // chop_top - Number of lines to chop from top of a comment // chop_bottom - Number of lines to chop from bottom of a comment // chop_left - Number of characters to chop from left // chop_right - Number of characters to chop from right // tabify - Leave tabs in comment text // untabify - Strip tabs and convert them into spaces. // ignore - Ignore comments // enable - Enable comments // // Inputs : // name - Name of style parameter // value - Optional parameter value // // Output : None // // Side Effects : Changes style of comment handler object. // // ----------------------------------------------------------------------------- void CommentHandler::style(char *name, char *value) { if (strcmp(name,"before") == 0) { location = BEFORE; } else if (strcmp(name,"after") == 0) { location = AFTER; } else if (strcmp(name,"skip") == 0) { if (value) skip_lines = atoi(value); } else if (strcmp(name,"chop_top") == 0) { if (value) chop_top = atoi(value); } else if (strcmp(name,"chop_bottom") == 0) { if (value) chop_bottom = atoi(value); } else if (strcmp(name,"chop_left") == 0) { if (value) chop_left = atoi(value); } else if (strcmp(name,"chop_right") == 0) { if (value) chop_right = atoi(value); } else if (strcmp(name,"tabify") == 0) { untabify = 0; } else if (strcmp(name,"untabify") == 0) { untabify = 1; } else if (strcmp(name,"ignore") == 0) { ignore = 1; } else if (strcmp(name,"enable") == 0) { ignore = 0; } } // ----------------------------------------------------------------------------- // void CommentHandler::parse_args(int argc, char **argv) // // Function for processing command line options given on the SWIG command line. // See the help string below for available options. // // Inputs : // argc = Argument count // argv = Argument strings // // Output : None // // Side Effects : // Changes various style parameters for the top-level CommentHandler. // ----------------------------------------------------------------------------- static char *comment_usage = "\ Comment Style Options : \n\ -Safter - Use comments after a declaration.\n\ -Sbefore - Use comments before a declaration.\n\ -Schop_bottom n - Chop n lines from bottom of comments.\n\ -Schop_left n - Chop n characters from left of a comment.\n\ -Schop_right n - Chop n characters from right of a comment.\n\ -Schop_top n - Chop n lines from top of comments.\n\ -Signore - Ignore comments.\n\ -Sskip n - Max lines between comment and declaration.\n\ -Stabify - Do not convert tabs.\n\ -Suntabify - Convert tabs into spaces (the default).\n\n"; void CommentHandler::parse_args(int argc, char **argv) { int i; for (i = 1; i < argc; i++) { if (argv[i]) { if (strcmp(argv[i],"-Sbefore") == 0) { this->style("before",0); mark_arg(i); } else if (strcmp(argv[i],"-Safter") == 0) { this->style("after",0); mark_arg(i); } else if (strcmp(argv[i],"-Schop_top") == 0) { if (argv[i+1]) { this->style("chop_top",argv[i+1]); mark_arg(i); mark_arg(i+1); i++; } else { arg_error(); } } else if (strcmp(argv[i],"-Schop_bottom") == 0) { if (argv[i+1]) { this->style("chop_bottom",argv[i+1]); mark_arg(i); mark_arg(i+1); i++; } else { arg_error(); } } else if (strcmp(argv[i],"-Schop_left") == 0) { if (argv[i+1]) { this->style("chop_left",argv[i+1]); mark_arg(i); mark_arg(i+1); i++; } else { arg_error(); } } else if (strcmp(argv[i],"-Schop_right") == 0) { if (argv[i+1]) { this->style("chop_right",argv[i+1]); mark_arg(i); mark_arg(i+1); i++; } else { arg_error(); } } else if (strcmp(argv[i],"-Sskip") == 0) { if (argv[i+1]) { this->style("skip",argv[i+1]); mark_arg(i); mark_arg(i+1); i++; } else { arg_error(); } } else if (strcmp(argv[i],"-Suntabify") == 0) { this->style("untabify",0); mark_arg(i); } else if (strcmp(argv[i],"-Stabify") == 0) { this->style("tabify",0); mark_arg(i); } else if (strcmp(argv[i],"-Signore") == 0) { this->style("ignore",0); } else if (strcmp(argv[i],"-help") == 0) { fputs(comment_usage,stderr); } } } }