/************************************************************************ * * * Copyright (c) 1982, Fred Fish * * All Rights Reserved * * * * This software and/or documentation is released for public * * distribution for personal, non-commercial use only. * * Limited rights to use, modify, and redistribute are hereby * * granted for non-commercial purposes, provided that all * * copyright notices remain intact and all changes are clearly * * documented. The author makes no warranty of any kind with * * respect to this product and explicitly disclaims any implied * * warranties of merchantability or fitness for any particular * * purpose. * * * ************************************************************************ */ /* * FILE * * dex0.c main routines for documentation extraction utility * * DESCRIPTION * * Contains the main routines and misc utility routines for dex. * * FUNCTIONS * * main entry point for dex utility * tolower convert alpha characters to lower case * options process options list on cmd line * usage give usage message and abort * indentation set indentation level * nofill turn off fill mode * fill turn on fill mode * asterisks form box top or bottom * emit_bline emit cmds for blank line * set_section lookup and set current section * reset_section reset current section (no section) * emit_filled emit text in filled mode * emit_unfilled emit text in unfilled mode * set_output switch output stream * * AUTHOR * * Fred Fish * */ /* * TOOL * * dex documentation extraction utility * * SYNOPSIS * * dex [-dhtv] [-r rcfile] file1 file2 file3 ... * * d => enable debug mode * h => print internal help message * t => enable trace mode * v => enable verbose mode * * r => use reconfiguration file * * DESCRIPTION * * Dex extracts internal documentation from program source * files, compiling it into a form suitable for input * to the nroff text formatter. It was developed primarily * to aid the author in maintaining "up to date" documentation * for each function in the portable math library (PML). * * For further information consult the document * "DEX - Documentation Extraction Utility". * * EXAMPLE * * The output of dex is typically collected in one * or more files to be inserted in the appropriate * places in other "skeleton" files during nroff * processing. * * For example, in the portable math library usage, * the following sequence of commands will rebuild * the library documentation to reflect the current * state of the library. * * cd /usr/public/pml * dex * * nroff -mm pml.r >pml.doc * * The command "dex *" extracts function and file documentation * from all the files in the library, and builds two output * files, "funcs.r" and "files.r". The "funcs.r" file * contains documentation on each of the library functions. * The "files.r" file contains documentation on each of the * files in the portable math library directory. The "pml.r" * file is an nroff file containing information common * to all files and functions. At the points where the * "funcs.r" and "files.r" files are to be inserted, there * is a ".so " nroff command. * */ /* * FILES * * ".dexrc" dex reconfiguration file [default] * * BUGS * * Items are currently collected as encountered in the files * specified in the command line. A planned future enhancement * is to sort the collected documentation regions by name. * * Lines which being with a "." character, such as the * line in the "FILES" section above, cause nroff to * occasionally do unintended things. * * The dynamic reconfiguration capability is relatively * primitive. Once the requirements are more clear, * a future version will be more flexible. * * AUTHOR * * Fred Fish * */ #include #include "hashtbl.h" #include "dex.h" /* * Some misc global variables. */ struct tbl_data *section=NULL; /* Current section being processed */ char *infile = "stdin"; /* Input file name if not stdin */ char *rcfile = ".dexrc"; /* Reconfiguration file */ int filled = TRUE; /* Flag for fill mode on */ int trace = FALSE; /* Trace flag */ int vflag = FALSE; /* Verbose flag */ int debug = FALSE; /* Debug flag */ extern int yydebug; /* YACC internal debug flag */ int indent_level = 0; /* Current indentation level */ int linenum = 1; /* Current input file line number */ FILE *infp = stdin; /* Lexical analysis input stream */ FILE *fp = stdout; /* Current output stream */ char title[128]; /* Title of current region */ /* * Some misc local variables. */ static int suppress = FALSE; /* Flag for region suppression */ static int box_emitted = FALSE; /* Flag for box has been emitted for region */ /* * The following documentation will be printed out when DEX is * invoked with the -h argument. */ static char *documentation[] = { "", "DESCRIPTION", "", "\tDex is a program for extracting documentation from program", "\tsource files, building files suitable for input to a", "\ttext formatter (such as nroff).", "", "USAGE", "", "\tdex [-dhtv] [-f rcfile] file1 file2 ...", "", "\t\t-d enable debug output (implies -v)", "\t\t-h print this help info", "\t\t-t enable trace output", "\t\t-v verbose mode", "", "\t\t-f use specified reconfiguration file", "\t\t (default \".dexrc\")", "", "OUTPUTS", "", "\tBy default DEX writes all output to stdout.", "\tThis can be changed via the reconfiguration file.", "", 0 }; /* * FUNCTION * * main DEX entry point * * KEY WORDS * * dex entry point * main * * SYNOPSIS * * main(argc,argv) * int argc; * char *argv[]; * * DESCRIPTION * * This is where DEX starts executing. All argument list * switches are processed first, then all the specified * files are processed. * */ /* * PSEUDO CODE * * Begin main * Process command line options. * For each argument list field * If field was not erased during option processing * If there is a previously processed file open * Close the previous file. * End if * If the current file cannot be opened then * Print error message. * Else * Save input file name for future use. * Reset current line number to 1. * Do any reconfiguration requested. * If verbose then tell user file name. * Process the input file. * Close the input file. * End if * End if * End for * Exit with "normal" status. * End main * */ main(argc, argv) int argc; char *argv[]; { FILE *fopen(); char *argp; int argnum; options(argc,argv); for (argnum = 1; argnum < argc; argnum++) { if ((argp = argv[argnum]) != NULL) { if (infp != NULL) { fclose(infp); } if ((infp = fopen(argp,"r")) == NULL) { perror(argp); } else { infile = argp; linenum = 1; reconfig(argp,rcfile); if (debug) {dump_tbl();} if (vflag) {printf("dex: processing \"%s\"\n",argp);} yyparse(); fclose(infp); } } } if (infp == stdin) { linenum = 1; reconfig(".",rcfile); yyparse(); } exit(NORMAL_EXIT); } /* * FUNCTION * * tolower force alphabetic characters to lower case * * SYNOPSIS * * char tolower(ch) * char ch; * * DESCRIPTION * * Tolower forces alphabetic upper case characters to lower case. * If the input character is already lower case, or is not * alphabetic, then it is simply returned unchanged. * * BUGS * * Currently works only if native character set is ASCII. * */ /* * PSEUDO CODE * * Begin tolower * If character is an upper case alphabetic then * Convert character to lower case and return it. * Else * Return character unchanged. * End if * End tolower * */ #ifndef tolower char tolower(ch) char ch; { if ('A' <= ch && ch <= 'Z') { return(ch + 040); } else { return(ch); } } #endif /* * FUNCTION * * options process command line options * * SYNOPSIS * * options(argc,argv) * int argc; * char *argv[]; * * DESCRIPTION * * Scans argument list, processing each switch as it is * found. The pointer to each switch string is then * replaced with a NULL to effectively erase the switch * argument. * */ /* * PSEUDO CODE * * Begin options * For each argument in the argument list * Get pointer to first char of argument. * If the argument is a switch then * Replace argument pointer with NULL. * Look at next argument character. * While there is another argument character * Switch on the argument character * Case "verbose": * Set verbose flag. * Break out of switch. * Case "debug": * Set debug flag. * Set YACC debug flag. * Break out of switch. * Case "reconfigure": * If reconfig file is next arg * If there is no next arg * Abort with usage message. * Else * Save file name. * If file is actually switch * Abort with usage message. * Else * Erase reconfig switch * End if * End if * Else * Save pntr to reconfig filename. * End if * Break out of switch. * Case "trace": * Set trace flag. * Break out of switch. * Default: * Abort with usage message. * End switch * End while * End if * End for * End options * */ options(argc, argv) int argc; char *argv[]; { int i; char c; /* 1st char of current command-line argument */ char *cp; /* current argument pointer */ for (i=1; i= argc) { usage(c); } else { rcfile = argv[i]; if (*rcfile == '-') { usage(c); } else { argv[i] = NULL; } } } else { rcfile = cp; } break; case 't': trace++; break; default: usage(c); } } } } } /* * FUNCTION * * usage give usage message and abort * * KEY WORDS * * usage * help processing * abort locations * * SYNOPSIS * * usage(ch) * char ch; (is h for explicit help) * * DESCRIPTION * * Usage is typically called when a problem has been * detected in the argument list, or usage help has * been explicitly requested (via the -h flag). * It prints an appropriate usage message and then aborts. * */ /* * PSEUDO CODE * * Begin usage * If character is not the explicit help character then * Print a brief usage message. * Print how to get more help. * Else * Initialize internal documentation pointer. * While there is a line of documentation to print * Print the line of documentation. * End while * End if * Exit with error status. * End usage * */ usage(c) char c; /* The char of the option */ { register char **dp; if (c != 'h') { printf("Usage: dex [-dhv] [-f rcfile]\n"); printf(" dex -h for help.\n"); } else { dp = documentation; while (*dp != NULL) { printf("%s\n",*dp++); } } exit(ERROR_EXIT); } /* * FUNCTION * * indentation set indentation to specified level * * KEY WORDS * * indent level * emit functions * * SYNOPSIS * * indentation(where) * int where; * * DESCRIPTION * * Checks requested indentation level against current * indentation level. If they do not match then * emits appropriate nroff command to set indentation * to the specified level. * */ /* * PSEUDO CODE * * Begin indentation * If current level is not that requested then * Emit command to change indentation. * Update the current indentation level. * End if * End indentation * */ indentation(where) int where; { char buffer[16]; if (indent_level != where) { sprintf(buffer,".in %d",where); emit(buffer,NULL); indent_level = where; } } /* * FUNCTION * * nofill if filled mode is on, turn it off * * KEY WORDS * * fill mode * emit functions * * SYNOPSIS * * nofill() * * DESCRIPTION * * Forces fill mode to be off. If fill mode is currently * on then emits suitable command to turn it off. * */ /* * PSEUDO CODE * * Begin nofill * If filled mode is on then * Emit command to turn it off. * Remember that it is now off. * End if * End nofill * */ nofill() { if (filled) { emit(".nf",NULL); filled = FALSE; } } /* * FUNCTION * * fill if fill mode is off, turn it on * * KEY WORDS * * fill mode * emit functions * * SYNOPSIS * * fill() * * DESCRIPTION * * Forces fill mode to be on. If fill mode is currently * off then emits suitable command to turn it on. * */ /* * PSEUDO CODE * * Begin fill * If filled mode is off then * Emit command to turn it on. * Remember that it is now on. * End if * End fill * */ fill() { if (!filled) { emit(".fi",NULL); filled = TRUE; } } /* * FUNCTION * * asterisks emit command for centered asterisks line * * KEY WORDS * * emit functions * box construction * * SYNOPSIS * * asterisks(how_many) * int how_many; * * DESCRIPTION * * Emits a command to center a line with the specified * number of asterisks. This is used to form the top * and bottom of a boxed item in the output. * */ /* * PSEUDO CODE * * Begin asterisks * For the specified number of asterisks * Accumulate asterisk in buffer. * End for * Null terminate buffer string. * Emit command to cause line to be centered. * Emit asterisks buffer. * End asterisks * */ asterisks(how_many) int how_many; { int count; char buffer[128]; char *bp; for (bp = buffer, count = 0 ; count < how_many; count++) { *bp++ = '*'; } *bp = NULL; emit(".ce",NULL); emit(buffer,'\\'); } /* * FUNCTION * * emit_bline emit command to form blank line * * KEY WORDS * * blank line * emit functions * * SYNOPSIS * * emit_bline() * * DESCRIPTION * * Emits a suitable command to form a blank line in the * output. Checks first to verify that output is not * being suppressed and the the current section is * enabled for processing. Finally, the current section * must be enabled for text emission. * */ /* * PSEUDO CODE * * Begin emit_bline * If there is a current section being processed then * If output is not suppressed and processing is enabled * If the section is enabled for text emission then * Emit command to form blank line. * End if * End if * End if * End emit_bline * */ emit_bline () { if (section != NULL) { if (!suppress && (section->flags & PROCESS)) { if (section->flags & EMITTEXT) { emit(".sp 1",NULL); } } } } /* * FUNCTION * * set_section lookup and set current section * * KEY WORDS * * emit functions * section selection * region suppression * * SYNOPSIS * * set_section(name) * char *name; * * DESCRIPTION * * Looks up the specified section and makes it the * current section. Resets the region * "box-emitted" and "suppress processing" flags. * Also sets the indentation level back to the left * margin and emits commands to print the section * name (underlined). * */ /* * PSEUDO CODE * * Begin set_section * Reset the box emitted flag for region. * Lookup the section and make it current. * If section name was found in table then * If the section begins a region then * If the section is to be processed * The region is processed also. * Switch output stream. * Emit cmd for indentation. * Set indentation to zero. * Emit cmd for filled mode. * Set filled mode. * Else * The region is suppressed. * End if * End if * If not suppressed and can be processed * Set indentation level back. * If new page is desired then * Emit command to start new page. * End if * If text emission is enabled then * If name is to be underlined * Emit underline command. * End if * Emit section name. * End if * End if * End if * End set_section * */ set_section(name) char *name; { struct tbl_data *tbl_find(); box_emitted = FALSE; if (debug) {printf("set_section: looking for \"%s\"\n",name);} section = tbl_find(name); if (section != NULL) { if (section->flags & REGION) { if (debug) {printf("set_section: REGION flag set\n");} if (section->flags & PROCESS) { if (debug) {printf("set_section: PROCESS flag set\n");} suppress = FALSE; set_output(section); emit(".in 0",NULL); indent_level = 0; emit(".fi",NULL); filled = TRUE; } else { suppress = TRUE; } } if (!suppress && (section->flags & PROCESS)) { if (debug) {printf("set_section: PROCESS flag set\n");} indentation(0); if (section->flags & EMITBP) { emit(".bp",NULL); } if (section->flags & EMITTEXT) { if (debug) {printf("set_section: EMITTEXT flag set\n");} if (section->flags & EMITUL) { emit(".ul 1",NULL); } emit(name,'\\'); } } } } /* * FUNCTION * * reset_section reset current section * * KEY WORDS * * sections * * SYNOPSIS * * reset_section(); * * DESCRIPTION * * Resets current section so that there is no section selected. * This is usually used when a non documentation line * (I.E code) is encountered. It terminates processing of the * current section. * */ /* * PSEUDO CODE * * Begin reset_section * Set current section to NULL. * End reset section * */ reset_section() { section = NULL; } /* * FUNCTION * * emit_filled emit text in filled mode * * KEY WORDS * * emit functions * text emission * * SYNOPSIS * * emit_filled(text) * char *text; * * DESCRIPTION * * Attempts to emit text in filled mode. If filled * mode is not enabled for the section then emits * the text in unfilled mode. Also attempts * to print the first field of the text in a box, * if the box output is enabled and has not yet been * emitted for the current documentation section. * */ /* * PSEUDO CODE * * Begin emit_filled * If there is a current section then * If section is not suppressed and can be processed * If box emission is enabled for section * If box has not yet been emitted then * Get string to box. * Emit command for blank lines. * Emit commands for box top. * Emit commands to center string. * Emit commands for box bottom. * Emit commands for blank lines. * Set the box emitted flag. * End if * End if * If text emission is enabled then * Set indentation level. * If section is enabled for fill then * Emit commands for fill. * Else * Emit commands for no fill. * End if * Emit the text. * End if * End if * End if * End emit_filled * */ emit_filled(text) char *text; { char buffer[128]; if (section != NULL) { if (!suppress && (section->flags & PROCESS)) { if (section->flags & EMITBOX) { if (!box_emitted) { xfield(title,text); emit(".sp 3",NULL); asterisks(strlen(title)+4); emit(".ce",NULL); sprintf(buffer,"* %s *",title); emit(buffer,'\\'); asterisks(strlen(title)+4); emit(".sp 3",NULL); box_emitted = TRUE; } } if (section->flags & EMITTEXT) { indentation(8); if (section->flags & EMITFILL) { fill(); } else { nofill(); } emit(text,'\\'); } } } } /* * FUNCTION * * emit_unfilled emit text in unfilled mode * * KEY WORDS * * text emission * emit functions * * SYNOPSIS * * emit_unfilled(text) * char *text; * * DESCRIPTION * * Emits text in unfilled mode providing there is * a current section, it is not being suppressed, * and it is enabled for processing and text emission. * */ /* * PSEUDO CODE * * Begin emit_unfilled * If there is a current section then * If section is not suppressed and can be processed * If section is enabled for text emission * Set nofill mode. * Set indentation level. * Emit text. * End if * End if * End if * End emit_unfilled * */ emit_unfilled(text) char *text; { if (section != NULL) { if (!suppress && (section->flags & PROCESS)) { if (section->flags & EMITTEXT) { nofill(); indentation(16); emit(text,'\\'); } } } } /* * FUNCTION * * set_output switch output stream * * KEY WORDS * * output switching * * SYNOPSIS * * set_output(region) * struct tbl_data *region; * * DESCRIPTION * * Tests to see if the region output has been redirected * to a file. If so, opens the file if necessary and * remembers the file pointer in the table entry structure. * * If no file is specified then the output goes to the * standard output. * * Initializes the global output pointer to point to * the stream where output goes. * */ /* * PSEUDO CODE * * Begin set_output * If the table entry pointer is valid then * If the region has no output file * Direct output to standard out. * Else * If the file is open then * Set output to go to file. * Else * If file open succeeds * Remember file pointer. * Else * Set output to stdout. * End if * End if * End if * End if * End set_output * */ set_output(region) struct tbl_data *region; { if (region != NULL) { if (region->out == NULL) { fp = stdout; } else { if (region->ofp != NULL) { fp = region->ofp; } else { if ((fp = fopen(region->out,"w")) != NULL) { region->ofp = fp; } else { fp = stdout; } } } } } /* * FUNCTION * * emit do actual output to current output stream * * KEY WORDS * * I/O functions * emit * * SYNOPSIS * * emit(text,quote) * char *text; * char quote; * * DESCRIPTION * * Calls appropriate operating system and/or library * functions to do the actual I/O. Is localized here * for both logical and portability reasons. * * Emit will automatically append a newline to the output * of each string. Also, if the quote character is not * null then all '\' and '.' characters will be preceeded by a * '\' character. * */ /* * PSEUDO CODE * * Begin emit * If text pointer is not invalid pointer then * While there is another character to emit * If quote flag set and char needs quoting * Emit quote character. * End if * Emit character. * End while * Emit newline character. * End if * End emit * */ emit(text,quote) char *text; char quote; { if (text != NULL) { while (*text != NULL) { if (quote != NULL && (*text == '.' || *text == '\\')) { fputc(quote,fp); } fputc(*text++,fp); } fputc('\n',fp); } } #ifndef unix perror (err) char *err; { if (err && *err) { fprintf (stderr, "%s: ", err); } fprintf (stderr, "\n"); } #endif unix