/* * * The purpose of this program is to extract the function descriptions * (prototypes) from C source code. It also provides for the creation * of documentation based on those prototypes. * * The specific reason for the creation of this program was to * provide a method for automatically creating header files to * describe all of the functions to be used within a multi-file * program. * * This file makes use of code in the companion files parse.c and io.c. * * Copyright (c) 1990, 1991, 1992 by Adam Bryant * * See Copyright notice in the file parse.c or in the manual page. * */ /* * Version history: * * For Version 0.30: file parse functions were separated out into a * separate file so that other programs could make use of them. The * functions descriptions are now stored in dynamic memory and * functions are provided to allow the manipulation of those * descriptions. * Version 0.31: Only a few minor adjustments were made to cextract... * only as much as was needed to build the cextdoc program using the * cproto.[ch] files. * Version 0.40: Totally rewrote the parsing code so that the use of * temporary files is unnecessary. Instead, two dynamic storage * elements have been created. Besides being much faster, this * method is also much more elegant than the prior method. * Version 0.41: Switched the NOSETBUFFER to a SETBUFFER flag. * Added in the ability to handle the vararg system to convert it * to the "..." for ANSI C. * * ... change notes for later versions in parse.c * * Note on Version 1.0: merged the cextract and cextdoc into one * program, with differential being made based on the command line * or in the program name. Version sent to comp.sources.reviewed. * */ #include "xtract.h" #ifndef VMS #include #include #else #include #include #endif /* VMS */ /* definition strings to be set when the C preprocessor runs */ #ifndef VAXC #define CPP_GIVEN "-D__CEXTRACT__" #define CPP_GIVEN2 "-D__CEXTDOC__" #else #define CPP_GIVEN "/define=__CEXTRACT__" #define CPP_GIVEN2 "/define=__CEXTDOC__" #endif /* VAXC */ /* output filter from the parsing routines */ void out_char(type, outch) int type, outch; { /* to keep track of where the output list is */ int tab_width = get_option(OPT_TABWIDTH); static P_BUFDATA curbuf[2] = { NULL, NULL }; static int curpos[2] = { 0, 0 }; static int tab_count = 0; /* split output properly */ switch (type) { case 0: /* documentation mode should never get this */ if (doc_extract != DOC_NONE) { fprintf(stderr, "Serious Internal Error: bad out_char() data\n"); exit(1); } case 1: /* give it to the first one */ if (tempbuf[type] == NULL) { /* start the list */ if ((tempbuf[type] = (P_BUFDATA) malloc(sizeof(S_BUFDATA))) == NULL) { fprintf(stderr, "Memory Allocation Error\n"); exit(1); } tempbuf[type]->next = NULL; curbuf[type] = tempbuf[type]; } /* have we got enough in this one? */ if (curpos[type] == MID_SIZE) { /* finish it off */ build_memory: curbuf[type]->data[MID_SIZE] = '\0'; curpos[type] = 0; /* start the next one */ if ((curbuf[type]->next = (P_BUFDATA) malloc(sizeof(S_BUFDATA))) == NULL) { fprintf(stderr, "Memory Allocation Error\n"); exit(1); } curbuf[type] = curbuf[type]->next; curbuf[type]->next = NULL; } /* special output filter for the documentation mode */ if (doc_extract != DOC_NONE) { /* tab expansion? */ if (tab_width > 0) { if (outch == '\t') { /* fill out tabs properly */ while (tab_count++ < tab_width) { /* check that it will fit */ if (curpos[type] == MID_SIZE) { tab_count--; goto build_memory; } /* put out the space */ curbuf[type]->data[(curpos[type])++] = ' '; } tab_count = 0; break; } else if (outch == '\\') { /* account for font changes */ tab_count -= 2; } else if (outch == '\n') { /* now on a new line */ tab_count = 0; } else { /* just count */ if (++tab_count == tab_width) { tab_count = 0; } } } } /* now store it */ curbuf[type]->data[(curpos[type])++] = outch; break; case -1: /* close everything up */ if (curbuf[0] != NULL) { curbuf[0]->data[curpos[0]] = '\0'; } if (curbuf[1] != NULL) { curbuf[1]->data[curpos[1]] = '\0'; } break; default: /* ain't one of mine */ fprintf(stderr, "Serious Internal Error: bad data to out_char()\n"); break; } } /* show the version of the program */ void show_version () { fprintf(stderr, "%s: version %d.%d, Copyright 1992 by Adam Bryant\n", prog_name, VERSION, PATCHLEVEL); } /* quickly show a sting and indicate if it is on or off */ static void quick_show (str, mode) char *str; int mode; { fprintf(stderr, "\t%s is %s.\n", str, mode ? "on":"off"); } /* show all of the settings */ void show_settings () { P_MACRO macro_temp; P_SUBST sub_tmp; /* give the version info and current settings */ show_version(); fprintf(stderr, "\n List of current settings:\n"); fprintf(stderr, "\tC preprocessor in use: %s\n", cpp_prog); switch (doc_extract) { case DOC_NONE: fprintf(stderr, "\tfunction prototype extraction mode.\n"); if (output_file[0][0] != '\0') { fprintf(stderr, "\tthe output file is \"%s\".\n", output_file[0]); } if (header_string[0] != '\0') { fprintf(stderr, "\tthe header string is \"%s\".\n", header_string); } quick_show("merged ANSI and K&R C output", get_option(OPT_COMPACT)); if (!get_option(OPT_COMPACT)) { quick_show("showing both ANSI and K&R C", get_option(OPT_BOTHUSE)); if (!get_option(OPT_BOTHUSE)) { quick_show("ANSI C output format", get_option(OPT_STDCUSE)); } } quick_show("showing prototypes at all times", get_option(OPT_SHOWANYWAY)); break; case DOC_NORMAL: fprintf(stderr, "\ttext documentation mode.\n"); if (output_file[1][0] != '\0') { fprintf(stderr, "\tthe output file is \"%s\".\n", output_file[1]); } break; case DOC_ROFF: fprintf(stderr, "\ttroff documentation mode.\n"); if (output_file[1][0] != '\0') { fprintf(stderr, "\tthe output file is \"%s\".\n", output_file[1]); } break; default: fprintf(stderr, "\tError, unknown mode.\n\n"); exit(1); break; } if (get_option(OPT_SORTMODE) == SORT_ALL) { quick_show("sorting for all functions", TRUE); } else if (get_option(OPT_SORTMODE) == SORT_FILE) { quick_show("sorting for each file", TRUE); } else { quick_show("sorting", FALSE); } if (get_option(OPT_WRAPPOINT) > 0) { fprintf(stderr, "\tlong prototype lists wrap at column %d.\n", get_option(OPT_WRAPPOINT)); } else { quick_show("wrapping of long prototypes", FALSE); } quick_show("separating function name and type", get_option(OPT_TYPEWRAP)); quick_show("showing first comment in each file", get_option(OPT_FIRSTCOMMENT)); quick_show("sending file name with first comment", get_option(OPT_PREPEND)); quick_show("comment capturing", get_option(OPT_COMMENTS)); if (get_option(OPT_STATICMODE) == ONLY_STATICS) { quick_show("only static functions are searched for", TRUE); } else { quick_show("including static functions in search", get_option(OPT_STATICMODE) == ANY_STATICS); } quick_show("prepending extern to function declarations", get_option(OPT_EXTERNS)); quick_show("testing for multi-line comments", !get_option(OPT_SINGLECOMMENTS)); /* now show all of the macro definitions */ if (macro_list != NULL) { fprintf(stderr, "\n List of preprocessor definitions:\n"); for (macro_temp = macro_list; macro_temp != NULL; macro_temp = macro_temp->next) { /* use? */ if ((macro_temp->usewhen != 2) && (macro_temp->usewhen != (doc_extract != DOC_NONE))) continue; fprintf(stderr, "\t%s\n", macro_temp->m_str); } } /* now check the substitutions */ if (subst_list != NULL) { fprintf(stderr, "\n List of prototype substitutions:\n"); for (sub_tmp = subst_list; sub_tmp != NULL; sub_tmp = sub_tmp->next) { /* used? */ if ((sub_tmp->usewhen != 2) && (sub_tmp->usewhen != (doc_extract != DOC_NONE))) continue; /* check it */ switch (sub_tmp->submode) { case SUBST_FULL: /* the full string needs replacing? */ fprintf(stderr, "\treplace all \"%s\" with \"%s\"\n", sub_tmp->from_str, sub_tmp->to_str); break; case SUBST_TYPE: /* the type only needs replacing? */ fprintf(stderr, "\treplace type \"%s\" with \"%s\"\n", sub_tmp->from_str, sub_tmp->to_str); break; case SUBST_NAME: /* the variable only needs replacing? WHY!? */ fprintf(stderr, "\treplace name \"%s\" with \"%s\"\n", sub_tmp->from_str, sub_tmp->to_str); break; } } } /* all done */ fprintf(stderr, "\n"); exit(0); } /* quick function to copy a "string" */ int copy_str (o_str, in_str) char *o_str, *in_str; { int ch, count = 0, ch_read = 0; /* find the beginning of the string */ while ((*in_str != '"') && (*in_str != '\0')) { ch_read++; in_str++; } /* now was a string found? */ if (*in_str == '\0') { return(-1); } ch_read++; in_str++; /* copy all of the string */ while (((ch = *(in_str)) != '\0') && (ch != '"')) { /* copy it */ o_str[count++] = ch; ch_read++; in_str++; } if (ch == '"') ch_read++; /* finish up and get out */ o_str[count] = '\0'; return(ch_read); } /* show the usage for the program */ void show_usage() { #ifndef VMS fprintf(stderr, "Usage: %s [-Q#] [+AaBbPpNnVvxZ -Hstr -Yprg -o ofile] [[-opts] file ... ]\n", prog_name); fprintf(stderr, "\t+A alphabetically sort all functions [incompatable with +C]\n"); fprintf(stderr, "\t+a alphabetically sort functions within each file [default off]\n"); fprintf(stderr, "\t-B build a system configuration file based on settings\n"); fprintf(stderr, "\t-b build a configuration file in the current directory\n"); fprintf(stderr, "\t+C include first comment from each file\n"); fprintf(stderr, "\t+c include comments immediately preceding functions\n"); fprintf(stderr, "\t-Dexpr send a macro definition to the C preprocessor\n"); fprintf(stderr, "\t+E add a preceding 'extern' to each function definition\n"); fprintf(stderr, "\t+F toggle prepending of file name to first comment [need +C]\n"); fprintf(stderr, "\t-f#cc in troff doc-mode set various font styles\n"); fprintf(stderr, "\t-Hstring build header file with read test on string\n"); fprintf(stderr, "\t-Idir add directory to search list for include files\n"); fprintf(stderr, "\t-o file send output to a file instead of standard output\n"); fprintf(stderr, "\t [take first non-flag as filename for output]\n"); fprintf(stderr, "\t+m allow multi-line comments when parsing comments\n"); fprintf(stderr, "\t-N do documentation output in troff ms format\n"); fprintf(stderr, "\t-n do documentation output in normal text\n"); fprintf(stderr, "\t+P create output in both ANSI and non-ANSI prototypes\n"); fprintf(stderr, "\t+p create output in ANSI C prototype format\n"); fprintf(stderr, "\t-Q# do not read in config files, or only those specified\n"); fprintf(stderr, "\t-qfile read in a specified configuration file\n"); fprintf(stderr, "\t+r remove variable names prior to output\n"); fprintf(stderr, "\t+S show parameters for non-ANSI portion of output\n"); fprintf(stderr, "\t+s include static function definitions\n"); fprintf(stderr, "\t-s=only capture only static function definitions\n"); fprintf(stderr, "\t-s=none exclude static function definitions\n"); fprintf(stderr, "\t-s=any include static function definitions\n"); fprintf(stderr, "\t-TN set tab width in doc-mode to N characters [0 = off]\n"); fprintf(stderr, "\t-Uname undefine the given macro name\n"); fprintf(stderr, "\t-V display the program settings then exit\n"); fprintf(stderr, "\t-v display version information then exit\n"); fprintf(stderr, "\t+W place function type on a separate line.\n"); fprintf(stderr, "\t+w# wrap long parameter output [dflt length 72]\n"); fprintf(stderr, "\t-x run program in extraction mode\n"); fprintf(stderr, "\t-Yprog specify program to use as a C preprocessor\n"); fprintf(stderr, "\t+Z create the output as a compact, merged list\n"); #else /* VMS */ fprintf(stderr, "USAGE: %s [/READ-CONFIG=#] [/OUTPUT=outfile] [[/OPTS] FILE ... ]\n", prog_name); #endif /* VMS */ exit(1); } /* set the value of a global option */ void set_option(which, how, value) Optype which; int how, value; { #ifdef CHECK_INPUTS /* check input values */ if ((which < 0) || (which >= OPT_NUMBER)) { fprintf(stderr, "SERIOUS ERROR: Internal option value out of range %d\n", which); exit(1); } if ((how < 0) || (how > 2)) { fprintf(stderr, "SERIOUS ERROR: Internal option method out of range %d\n", how); exit(1); } #endif /* CHECK_INPUTS */ /* now set properly */ if (how == 2) { global_opts[0][which] = value; global_opts[1][which] = value; } else { global_opts[how][which] = value; } } /* retrieve the value of a global option */ int get_option(which) Optype which; { #ifdef CHECK_INPUTS /* check input values */ if ((which < 0) || (which >= OPT_NUMBER)) { fprintf(stderr, "SERIOUS ERROR: Internal option value out of range %d\n", which); exit(1); } #endif /* CHECK_INPUTS */ /* return whichever setting is needed */ if (doc_extract == DOC_NONE) { return(global_opts[0][which]); } return(global_opts[1][which]); } /* set configuration settings based on the initial name */ static void cext_init() { /* enter the proper mode */ if (strncmp(CEXTDOC_NAME, prog_name, strlen(prog_name)) == 0) { doc_extract = DOC_NORMAL; } else { doc_extract = DOC_NONE; } /* set documentation mode options properly */ set_option(OPT_COMMENTS, 1, TRUE); set_option(OPT_STATICMODE, 1, ANY_STATICS); set_option(OPT_EXTERNS, 1, FALSE); set_option(OPT_STDCUSE, 1, TRUE); set_option(OPT_SORTMODE, 1, SORT_FILE); /* set extraction mode options now */ set_option(OPT_COMMENTS, 0, FALSE); set_option(OPT_STATICMODE, 0, NO_STATICS); set_option(OPT_EXTERNS, 0, TRUE); set_option(OPT_STDCUSE, 0, FALSE); set_option(OPT_SORTMODE, 0, SORT_NONE); /* now set common options */ set_option(OPT_NONAMES, 2, FALSE); set_option(OPT_COMPACT, 2, FALSE); set_option(OPT_TYPEWRAP, 2, FALSE); set_option(OPT_WRAPPOINT, 2, 0); set_option(OPT_BOTHUSE, 2, TRUE); set_option(OPT_SINGLECOMMENTS, 2, TRUE); set_option(OPT_FIRSTCOMMENT, 2, FALSE); set_option(OPT_SHOWANYWAY, 2, TRUE); set_option(OPT_PREPEND, 2, FALSE); set_option(OPT_TABWIDTH, 2, 8); } /* return FALSE if character is not a normal part of a filename */ int fname_char(ch) int ch; { switch (ch) { #ifdef MS_DOS case '\\': #endif /* MS_DOS */ #ifdef VMS case ']': case ':': #endif /* VMS */ case '/': /* nope */ return(FALSE); } return(TRUE); } /* routine to build the macros onto the prototype line */ static void append_macros(str_out) char *str_out; { P_MACRO macro_temp; #ifndef VAXC for (macro_temp = macro_list; macro_temp != NULL; macro_temp = macro_temp->next) { /* check for use */ if ((macro_temp->usewhen != 2) && (macro_temp->usewhen != (doc_extract != DOC_NONE))) continue; /* use it */ strcat(str_out, macro_temp->m_str); strcat(str_out, " "); } #else char und_str[MID_SIZE], def_str[MID_SIZE], inc_str[MID_SIZE]; int und_uses = 0, def_uses = 0, inc_uses = 0; int und_len = strlen(UNDEF_LEADER); int inc_len = strlen(UNDEF_LEADER); int def_len = strlen(DEF_LEADER); /* initialize */ und_str[0] = '\0'; def_str[0] = '\0'; inc_str[0] = '\0'; /* properly build the command line for the C compiler */ for (macro_temp = macro_list; macro_temp != NULL; macro_temp = macro_temp->next) { /* check for use */ if ((macro_temp->usewhen != 2) && (macro_temp->usewhen != (doc_extract != DOC_NONE))) continue; /* check for the definition statement */ if (strncmp(DEF_LEADER, macro_temp->m_str, def_len) == 0) { if (def_uses++ > 0) { strcat(def_str, ","); } if (macro_temp->m_str[def_len] == '(') { strcat(def_str, macro_temp->m_str + def_len + 1); def_str[strlen(def_str) - 1] = '\0'; def_uses++; } else { strcat(def_str, macro_temp->m_str + def_len); } } else if (strncmp(UNDEF_LEADER, macro_temp->m_str, und_len) == 0) { if (und_uses++ > 0) { strcat(und_str, ","); } if (macro_temp->m_str[und_len] == '(') { strcat(und_str, macro_temp->m_str + und_len + 1); und_str[strlen(und_str) - 1] = '\0'; def_uses++; } else { strcat(und_str, macro_temp->m_str + und_len); } } else if (strncmp(INC_LEADER, macro_temp->m_str, inc_len) == 0) { if (inc_uses++ > 0) { strcat(inc_str, ","); } if (macro_temp->m_str[inc_len] == '(') { strcat(inc_str, macro_temp->m_str + inc_len + 1); und_str[strlen(inc_str) - 1] = '\0'; def_uses++; } else { strcat(inc_str, macro_temp->m_str + inc_len); } } else { strcat(str_out, macro_temp->m_str); } } /* now finish everything off */ if (def_uses > 0) { strcat(str_out, DEF_LEADER); if (def_uses > 1) strcat(str_out, "("); strcat(str_out, def_str); if (def_uses > 1) strcat(str_out, ")"); } if (und_uses > 0) { strcat(str_out, UNDEF_LEADER); if (und_uses > 1) strcat(str_out, "("); strcat(str_out, und_str); if (und_uses > 1) strcat(str_out, ")"); } if (inc_uses > 0) { strcat(str_out, INC_LEADER); if (inc_uses > 1) strcat(str_out, "("); strcat(str_out, inc_str); if (inc_uses > 1) strcat(str_out, ")"); } strcat(str_out, " "); #endif /* VAXC */ } /* control program flow and command line options */ int main (argc, argv) int argc; char **argv; { char cur_argstr[MID_SIZE], sysl_out[MID_SIZE]; extern int getpid(); struct stat fst; int count, j, len; /* initialize some things */ fpout = stdout; len = strlen(argv[0]); for (j = len - 1; j > 0; j--) { if (fname_char(argv[0][j]) == FALSE) { j++; break; } } strcpy(prog_name, &(argv[0][j])); out_filenum = 0; start_comment[0] = '\0'; header_string[0] = '\0'; cur_cfg_file[0] = '\0'; output_file[0][0] = '\0'; output_file[1][0] = '\0'; cfg_file[0] = '\0'; sprintf(cpp_prog, "%s %s", CPP, CPP_COMMENTS); macro_list = last_macro = NULL; cext_init(); /* check for config file flags */ count = 1; if ((count < argc) && is_switch(argv[count][0])) { #ifndef VMS /* are we skipping config files? */ if (argv[count][1] == 'Q') { /* check the type */ if (isdigit(argv[count][2])) { cfg_switch = argv[count][2] - '0'; if ((cfg_switch < 0) || (cfg_switch > 7) || (argv[count][3] != '\0')) { fprintf(stderr, "Invalid value for -Q flag\n"); show_usage(); } } else if (argv[count][2] != '\0') { fprintf(stderr, "Invalid data following the -Q flag\n"); show_usage(); } else { cfg_switch = 0; } /* processed this one */ count++; } else { #endif /* VMS */ /* now test for full length string */ if (minmatch_str(&(argv[count][1]), "read-config", 4)) { len = strlen(argv[count]); for (j = 4; j < len; j++) { if ((argv[count][j] == ':') || (argv[count][j] == '=')) { j++; break; } } if (j == len) { cfg_switch = 0; } else if (isdigit(argv[count][j])) { cfg_switch = argv[count][j] - '0'; if ((cfg_switch < 0) || (cfg_switch > 7) || (argv[count][j + 1] != '\0')) { fprintf(stderr, "Invalid value for -read-config flag\n"); show_usage(); } } else { fprintf(stderr, "Invalid data following the -read-config flag\n"); show_usage(); } count++; } #ifndef VMS } #endif /* VMS */ } /* now do the configurations */ add_macro(2, CPP_GIVEN); add_macro(1, CPP_GIVEN2); do_config(); /* go through the entire argument list */ while (count < argc) { /* check for any switches */ if (is_switch(argv[count][0])) { /* call the routine for command line parsing */ if (argv[count][0] == '-') { j = FALSE; } else { j = TRUE; } strcpy(cur_argstr, &(argv[count][1])); parse_cmd(cur_argstr, j, TRUE); } else { /* treat it as a file name */ if (out_filenum == 1) { /* store the output file name */ strcpy(output_file[0], argv[count]); strcpy(output_file[1], argv[count]); out_filenum = 2; } else { /* check if both C and A are on */ if (get_option(OPT_FIRSTCOMMENT) && (get_option(OPT_SORTMODE) == SORT_ALL)) { fprintf(stderr, "Sorting all functions and displaying file names does not mix\n"); show_usage(); } /* yes, a file has been parsed */ out_filenum = 3; /* process the file for input */ strcpy(file_name, argv[count]); strcpy(errout_filename, file_name); line_count = 1; /* check if the file exits */ if (stat( file_name, &fst ) != 0) { sprintf(cur_argstr, "error: could not access file <%s>", file_name); err_msg(cur_argstr); exit(1); } else if ((fpin = fopen(file_name, "r")) == NULL) { sprintf(cur_argstr, "error: could not read file <%s>", file_name); err_msg(cur_argstr); exit(1); } fclose(fpin); /* build /lib/cpp line */ sprintf(sysl_out, "%s ", cpp_prog); append_macros(sysl_out); strcat(sysl_out, file_name); /* open it */ if ((fpin = open_input(sysl_out, "r")) == NULL) { sprintf(tmp_str, "unable to open CPP \"pipe\" to file <%s>", file_name); err_msg(tmp_str); exit(1); } #ifdef SETBUFFER /* now set the buffer */ setbuffer(fpin, inbuffer, BUFFER_SIZE); #endif /* SETBUFFER */ /* now do it */ parse_file(); files_parsed++; if (get_option(OPT_SORTMODE) != SORT_ALL) { send_file(); } close_input(fpin); } } /* now go to the next argument */ count++; } /* check if -o flag is unprocessed */ if (out_filenum == 1) { fprintf(stderr, "No output file specified\n"); show_usage(); } if ((out_filenum == 2) || (out_filenum == 0)) { fprintf(stderr, "No files for parsing specified\n"); show_usage(); } /* send out everything, if it hasn't been done */ if (get_option(OPT_SORTMODE) == SORT_ALL) { send_file(); } /* now close things up and combine if needed */ cxt_close(); exit(0); return(0); } /* transmit the currently stored comment to the output file */ void send_first_comment(begin_str) char *begin_str; { int mode; /* check for last comment */ if (start_comment[0] != '\0') { /* check the mode */ if (doc_extract == DOC_NONE) { mode = 0; } else { mode = 1; } /* give the header for troff output */ dont_space = FALSE; if (doc_extract == DOC_ROFF) { start_block = TRUE; init_roff(1); if (total_out > 0) { out_str(1, ".bp\n"); out_str(1, ".sp 0.5i\n"); } out_str(1, ".KS\n"); out_str(1, ".nf\n"); out_str(1, ".ft 2\n"); } else { out_char(mode, '\n'); } /* duplicate if needed */ if (!get_option(OPT_COMPACT) && (doc_extract == DOC_NONE) && (get_option(OPT_SHOWANYWAY) && get_option(OPT_BOTHUSE))) { out_char(1, '\n'); out_str(0, begin_str); out_str(1, begin_str); out_str(0, start_comment); out_str(1, start_comment); out_char(0, '\n'); out_char(1, '\n'); } else { out_str(mode, begin_str); out_str(mode, start_comment); out_char(mode, '\n'); } } } #ifdef TEST /* dummy function that will never be used but makes a nice test */ char (*(*test_it())[])(same_foo, x, y, fpfoo, z, fpp2) int *x, z; FILE* fpfoo, fpp2; char (*(*same_foo())[])(); int *y; { /* As scary a bit of code as I could think of. Is it valid? */ } #endif /*TEST*/