% % Tango/Weevil - A WEB Tangler and Weaver % Copyright (C) 1995 Corey Minyard % % This program is free software; you can redistribute it and/or modify % it under the terms of the GNU General Public License as published by % the Free Software Foundation; either version 2 of the License, or % (at your option) any later version. % % This program is distributed in the hope that it will be useful, % but WITHOUT ANY WARRANTY; without even the implied warranty of % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the % GNU General Public License for more details. % % You should have received a copy of the GNU General Public License % along with this program; if not, write to the Free Software % Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. % % Corey Minyard - minyard@metronet.com % \section{Weevil Main File Intro} The following code holds the main file structure: @includefile weevinfo.xfr %The following holds cross references for all the file used by weevil @reffile weevilref.xfr @reffile c_weevil.xfr @reffile latex.xfr @code <*> @ @ @ @endcode \subsection{Includes} Just some standard stuff to include. The \verb|weevinfo| file contains all the major data structures for weevil. @code #include #include #include #include #include "weevinfo.h" @endcode \subsection{Defines} Since all the language and output specific information is not in this file, not much is left to define. @code /* Return values for the get_input_line routine. */ #define GOT_LINE 1 #define END_OF_FILE 0 /* These are for input_xreffile(). */ #define INCLUDE_TYPE 1 #define XREF_TYPE 2 #define MAIN_TYPE 3 #define MAX_FILENAME_LENGTH 200 @endcode \subsection{Code} Herer is the meat of the matter here. The actual code. @code @ @ @ @ @ @ @ @
@endcode \subsubsection{Utils} These are various utilities that may be used both inside and outside this module. @code @ @ @ @ @ @endcode \begin{twproc}{r\_strtok} \Description A re-entrant strtok. This routine scans the string \verb|data| for tokens. These tokens are separated by characters in the \verb|sstr| string. A pointer to the next token and a starting place for the next \verb|r_strtok| call is returned. \SideEffects The string in the \verb|data| variable is modified by insertion of null characters (\verb|'\0'|) to separate the tokens. \ReturnValues \verb|char *| - A pointer to the next token. \Inputs \begin{twparmlist} \item[data] The string to scan for token \item[sstr] A string containing characters that should separate the tokens. \end{twparmlist} \Outputs \begin{twparmlist} \item[next\_data] A pointer to the next place to start scanning. This may be used as the next \verb|data| parameter to get successive tokens from a string. \end{twparmlist} \StartCode @code char * r_strtok(char *data, char *sstr, char **next_data) { char *retval; while ( (*data != '\0') && (strchr(sstr, *data) != NULL)) { data++; } retval = data; while ( (*data != '\0') && (strchr(sstr, *data) == NULL)) { data++; } if (retval == data) { retval = NULL; } else { if (*data != '\0') { *data = '\0'; data++; } if (next_data != NULL) { *next_data = data; } } return(retval); } @endcode \end{twproc} \begin{twproc}{stralloc} \Description Allocates some memory for a string and copies the string into it. \ReturnValues \verb|char *| - The newly allocated string. \Inputs \begin{twparmlist} \item[str] The string to allocate the data for and copy data into. \end{twparmlist} \StartCode @code char * stralloc(char *str) { char *retval; retval = malloc(strlen(str) + 1); if (retval == NULL) { fprintf(stderr, "Fatal: Unable to allocate memory\n"); exit(1); } strcpy(retval, str); return(retval); } @endcode \end{twproc} \begin{twproc}{get\_input\_line} \Description Get a line of text from the input file. If the line is too long, it will truncate the line and skip everything after the maximum line size. \SideEffects The input file is read from. The current line number is advanced. The current input line string is set to the new input line. \ReturnValues \verb|int| - This will be either \verb|END_OF_FILE| or \verb|GOT_LINE|. \Inputs \begin{twparmlist} \item[lpwd] Holds ``global'' data for the program \end{twparmlist} \StartCode @code int get_input_line(t_lpweevildat *lpwd) { #define FLUSHSIZE 20 int retval; int length; char flushbuf[FLUSHSIZE]; int flushlength; (lpwd->curr_lineno)++; if (fgets(lpwd->line, lpwd->maxlinesize, lpwd->infile) == NULL) { retval = END_OF_FILE; /* Did not get a line, end of file. */ } else { retval = GOT_LINE; length = strlen(lpwd->line); /* If the line was too long (it was the max size and the last character was not a newline), print a log and flush to end of line. */ if ( (length == (lpwd->maxlinesize - 1)) && (lpwd->line[length-1] != '\n')) { fprintf(stderr, "Warning, line %d too long," " flushing to end of line\n", lpwd->curr_lineno); if (fgets(flushbuf, FLUSHSIZE, lpwd->infile) == NULL) { flushlength = 0; } else { flushlength = strlen(flushbuf); } while ( (flushlength == (FLUSHSIZE - 1)) && (lpwd->line[flushlength-1] != '\n')) { if (fgets(flushbuf, FLUSHSIZE, lpwd->infile) == NULL) { flushlength = 0; } else { flushlength = strlen(flushbuf); } } } else { lpwd->line[length-1] = '\0'; /* Get rid of the newline. */ } } return(retval); } @endcode \end{twproc} \begin{twproc}{find\_name\_in\_xref} \Description Finds a name (string in a name field) the same as a specified string. \ReturnValues \verb|xreflist *| - A pointer to the list item holding the name. \Inputs \begin{twparmlist} \item[start] The list start \item[name] The name to search for \end{twparmlist} \StartCode @code xreflist * find_name_in_xref(xreflist *start, char *name) { while (start != NULL) { if (strcmp(start->name, name) == 0) { break; } start = start->next; } return(start); } @endcode \end{twproc} \begin{twproc}{find\_macro\_in\_xref} \Description Finds a macro name (string in a macro field) the same as a specified string. \ReturnValues \verb|xreflist *| - A pointer to the list item holding the macro name. \Inputs \begin{twparmlist} \item[start] The list start \item[name] The macro name to search for \end{twparmlist} \StartCode @code xreflist * find_macro_in_xref(xreflist *start, char *macro) { while (start != NULL) { if ( (start->file == NULL) && (strcmp(start->macro, macro) == 0)) { break; } start = start->next; } return(start); } @endcode \end{twproc} \subsubsection{Command Processing} All commands encountered (commands start with \verb|@| and must start in column 1) while processing documentation will be handled by this code. \begin{twproc}{process\_cmd} \Description Handles a \verb|@| command while in documentation (not code) state. \SideEffects \begin{twparmlist} \item[codestate] \item[curr\_filename] \item[curr\_macro] \end{twparmlist} \Inputs \begin{twparmlist} \item[lpwd] Holds ``global'' data for the program \end{twparmlist} \StartCode @code void process_cmd(t_lpweevildat *lpwd) { char *strhold; char *cmd; char *name; int namelength; cmd = r_strtok(&(lpwd->line[1]), " \t", &strhold); if (cmd == NULL) { fprintf(stderr, "Error on line %d: Missing command\n", lpwd->curr_lineno); lpwd->retcode = 2; } @ @ else if (strcmp(cmd, "includefile") == 0) { name = r_strtok(strhold, " \t", &strhold); input_xreffile(lpwd, name, INCLUDE_TYPE); } else if (strcmp(cmd, "reffile") == 0) { name = r_strtok(strhold, " \t", &strhold); input_xreffile(lpwd, name, XREF_TYPE); } else if (strcmp(cmd, "line") == 0) { /* Ignored when weevilling. */ } else { fprintf(stderr, "Error on line %d: Invalid command\n", lpwd->curr_lineno); lpwd->retcode = 2; } } @endcode The following code handle a \verb|@code| command. It will get the name of the code macro (between \verb|<| and \verb|>|) and call the output routines to handle macro beginnings. @code else if (strcmp(cmd, "code") == 0) { if (lpwd->codestate == IN_CODE) { fprintf(stderr, "Error on line %d:" " Macro begin when already processing a macro\n", lpwd->curr_lineno); lpwd->retcode = 2; } else { while ((*strhold == ' ') || (*strhold == '\t')) { strhold++; } if (*strhold != '<') { fprintf(stderr, "Error on line %d:" " Macro name must be surrounded by <>\n", lpwd->curr_lineno); lpwd->retcode = 2; } else { strhold++; } name = r_strtok(strhold, ">", &strhold); lpwd->curr_macro = stralloc(name); lpwd->codestate = IN_CODE; lpwd->begin_macro(lpwd, name); } } @endcode The following code handles a file command. It scans for the file name and sets the current file name to the name specified in the command. @code else if (strcmp(cmd, "file") == 0) { if (lpwd->codestate == IN_CODE) { fprintf(stderr, "Error on line %d:" " File name change not allowed in code\n", lpwd->curr_lineno); lpwd->retcode = 2; } else { name = r_strtok(strhold, " \t", &strhold); if (name == NULL) { fprintf(stderr, "Error on line %d:" " File name not given with file command\n", lpwd->curr_lineno); lpwd->retcode = 2; } lpwd->curr_filename = stralloc(name); namelength = strlen(name); } } @endcode \end{twproc} \subsubsection{Process Code} Output code goes through the following routine where it will be output by the mode-dependent output routines. \begin{twproc}{process\_codeline} \Description Processed a line of code. This will scan for \verb|@<>| macro uses in the code and format the macro name. It will also convert \verb|@@| in the input to \verb|@|. \SideEffects \begin{twparmlist} \item[code\_lineno] \end{twparmlist} \Inputs \begin{twparmlist} \item[lpwd] Holds ``global'' data for the program \end{twparmlist} \StartCode @code void process_codeline(t_lpweevildat *lpwd) { char *cp; char *macro_name; int code_proc; cp = lpwd->line; lpwd->output_linenum(lpwd); lpwd->code_lineno++; while (*cp != '\0') { code_proc = lpwd->handle_char(lpwd, *cp, *(cp+1)); if (code_proc == PROCESS_CHAR_INSTRING) { lpwd->output_char(lpwd, *cp); cp++; } else if (code_proc == PROCESS_CHAR_SKIPNEXT) { lpwd->output_char(lpwd, *cp); cp++; lpwd->output_char(lpwd, *cp); cp++; } else { switch(*cp) { case '@': cp++; switch(*cp) { case '@': lpwd->output_char(lpwd, '@'); lpwd->output_char(lpwd, '@'); cp++; break; case '<': cp++; macro_name = r_strtok(cp, ">", &cp); lpwd->output_macro_use(lpwd, macro_name); break; default: fprintf(stderr, "Error on line %d:" " Invalid @ operator: %c\n", lpwd->curr_lineno, *cp); lpwd->retcode = 2; break; } break; default: lpwd->output_char(lpwd, *cp); cp++; } } } lpwd->output_char(lpwd, '\n'); } @endcode \end{twproc} \subsubsection{Cross Reference Routines} These routine handle inputting and outputting cross-references. \begin{twproc}{output\_xref} \Description Outputs the cross references for a macro. This routine will scan all cross reference lists for the given macro name. Uses references will be output with the declaration location(s), if available. All other references will just be output. Many output-mode dependent handlers do the dirty work of the output; they are called form this routine. \Inputs \begin{twparmlist} \item[lpwd] Holds ``global'' data for the program \end{twparmlist} \StartCode @code void output_xref(t_lpweevildat *lpwd) { xreflist *list; xreflist *def; bool declares_output;/* Since declarations can come from multiple sources, this tracks if any declarations have been output so appropriate cleanup can be done. */ bool any_xrefs; /* This tells if any references have been output. This is used because a special output routine is called if any references have been output. */ any_xrefs = FALSE; list = find_macro_in_xref(lpwd->uses, lpwd->curr_macro); if (list != NULL) { any_xrefs = TRUE; lpwd->output_begin_uses(lpwd); while (list != NULL) { def = find_name_in_xref(lpwd->pounddefs, list->name); if (def == NULL) { def = find_name_in_xref(lpwd->vardefs, list->name); } lpwd->output_use_start(lpwd, list->name); while (def != NULL) { lpwd->output_use_ref(lpwd, def->file, def->macro, list->name); def = find_name_in_xref(def->next, list->name); if (def != NULL) { lpwd->output_between_use_refs(lpwd, list->name); } } lpwd->output_use_end(lpwd, list->name); list = find_macro_in_xref(list->next, lpwd->curr_macro); } lpwd->output_end_uses(lpwd); } declares_output = FALSE; list = find_macro_in_xref(lpwd->vardefs, lpwd->curr_macro); if (list != NULL) { any_xrefs = TRUE; declares_output = TRUE; lpwd->output_begin_decls(lpwd); while (list != NULL) { lpwd->output_decl(lpwd, list->name); list = find_macro_in_xref(list->next, lpwd->curr_macro); } } list = find_macro_in_xref(lpwd->pounddefs, lpwd->curr_macro); if (list != NULL) { any_xrefs = TRUE; if (!declares_output) { declares_output = TRUE; lpwd->output_begin_decls(lpwd); } while (list != NULL) { lpwd->output_decl(lpwd, list->name); list = find_macro_in_xref(list->next, lpwd->curr_macro); } } if (declares_output) { lpwd->output_end_decls(lpwd); } if (any_xrefs) { lpwd->output_after_xrefs(lpwd); } } @endcode \end{twproc} \begin{twproc}{open\_include\_file} \Description Opens a cross-reference file, looking in the include list if necessary. \Inputs \begin{twparmlist} \item[lpwd] Holds ``global'' data for the program \item[filename] The file name to open \end{twparmlist} \StartCode @code FILE * open_include_file(t_lpweevildat *lpwd, char *filename) { xreflist *includes; FILE *file; char fname[MAX_FILENAME_LENGTH+1]; int filename_length; filename_length = strlen(filename); file = NULL; if (filename[0] != '/') { includes = lpwd->includes; while ((file == NULL) && (includes != NULL)) { if ( (strlen(includes->name) + filename_length + 1) > MAX_FILENAME_LENGTH) { fprintf(stderr, "Warning: Include path + filename too long: %s/%s\n", includes->name, filename); } else { strcpy(fname, includes->name); strcat(fname, "/"); strcat(fname, filename); file = fopen(fname, "r"); } includes = includes->next; } } if (file == NULL) { file = fopen(filename, "r"); } return(file); } @endcode \end{twproc} \begin{twproc}{input\_xreffile} \Description Reads a cross reference file and store the references in the reference tables. \SideEffects \begin{twparmlist} \item[pounddefs] \item[vardefs] \item[uses] \end{twparmlist} \Inputs \begin{twparmlist} \item[lpwd] Holds ``global'' data for the program \item[filename] The file name the references are to be read from. \item[incltype] The type of include. This may be \begin{twparmlist} \item[INCLUDE\_TYPE] a \verb|@includefile| \item[XREF\_TYPE] a \verb|@reffile| \item[MAIN\_TYPE] the cross reference file for the file being processed. \end{twparmlist} \end{twparmlist} \StartCode @code void input_xreffile(t_lpweevildat *lpwd, char *filename, int incltype) { char *line; FILE *in_file; char linebuf[MAXLINESIZE]; xreflist *elem; char *curr_macro; char *actfilename; curr_macro = "invalid"; in_file = open_include_file(lpwd, filename); if (in_file == NULL) { fprintf(stderr, "Unable to open xref file: %s\n", filename); } else { line = fgets(linebuf, MAXLINESIZE, in_file); if ( (line == NULL) || (strncmp(line, "f ", 2) != 0)) { fprintf(stderr, "Invalid xref file: %s\n", filename); fclose(in_file); } else { if (incltype == MAIN_TYPE) { actfilename = NULL; } else { actfilename = stralloc(r_strtok(&(line[2]), "\n", NULL)); } line = fgets(linebuf, MAXLINESIZE, in_file); while (line != NULL) { elem = malloc(sizeof(*elem)); elem->file = actfilename; elem->macro = curr_macro; if (elem == NULL) { fprintf(stderr, "Unable to allocate enough memory\n"); exit(1); } if (strncmp(line, "f ", 2) == 0) { actfilename = stralloc(r_strtok(&(line[2]), "\n", NULL)); } else if (strncmp(line, "u ", 2) == 0) { if (incltype == MAIN_TYPE) { elem->name = stralloc(r_strtok(&(line[2]), "\n", NULL)); elem->next = lpwd->uses; lpwd->uses = elem; } } else if (strncmp(line, "s ", 2) == 0) { if ( (incltype == MAIN_TYPE) || (incltype == INCLUDE_TYPE)) { elem->name = stralloc(r_strtok(&(line[2]), "\n", NULL)); elem->next = lpwd->vardefs; lpwd->vardefs = elem; } } else if (strncmp(line, "d ", 2) == 0) { if ( (incltype == MAIN_TYPE) || (incltype == INCLUDE_TYPE)) { elem->name = stralloc(r_strtok(&(line[2]), "\n", NULL)); elem->next = lpwd->pounddefs; lpwd->pounddefs = elem; } } else if (strncmp(line, "e ", 2) == 0) { elem->name = stralloc(r_strtok(&(line[2]), "\n", NULL)); elem->next = lpwd->vardefs; lpwd->vardefs = elem; } else if (strncmp(line, "m ", 2) == 0) { curr_macro = stralloc(r_strtok(&(line[2]), "\n", NULL)); free(elem); } else { fprintf(stderr, "Invalid prefix (%c%c) in xreffile: %s\n", line[0], line[1], filename); free(elem); } line = fgets(linebuf, MAXLINESIZE, in_file); } } } } @endcode \end{twproc} \subsubsection{Get Args} The following code will process the command line for arguments. @code @ @ @ @ @endcode \begin{twproc}{get\_arg\_str} \Description Returns a parameter for an argument. If the argument is in the same string as the command, that will be returned. Otherwise, the next argument will be returned. \ReturnValues \verb|static char *| - A pointer to the argument parameter. \Inputs \begin{twparmlist} \item[argidx] A index for the current argument. \item[argc] The argument count \item[argv] The arguments \item[start\_offset] The character offset into the current argument that is right after the command finishes. \end{twparmlist} \Outputs \begin{twparmlist} \item[argidx] The next argument \end{twparmlist} \StartCode @code static char * get_arg_str(int *argidx, int argc, char *argv[], int start_offset) { char *retval; if (argv[*argidx][start_offset] == '\0') { (*argidx)++; if ((*argidx) == argc) { retval = NULL; } else { retval = argv[*argidx]; } } else { retval = &(argv[*argidx][start_offset]); } return(retval); } @endcode \end{twproc} The following holds a list of languages that are supported and a list of output modes that are supported. Each language and output mode has a set of handler routines that are given in these lists. @code static struct s_langs { char *lang_name; char_handler handler; handler_init init; } langs[] = { { "c", &c_handle_char, &init_c_lang } }; static const num_langs = sizeof(langs) / sizeof(struct s_langs); static struct s_modes { char *mode_name; mode_init init; begin_macro_handler begin_macro; end_macro_handler end_macro; finalend_chunk_handler finalend_chunk; output_macro_use_handler output_macro_use; output_linenum_handler output_linenum; output_char_handler output_char; output_begin_uses_handler output_begin_uses; output_end_uses_handler output_end_uses; output_use_start_handler output_use_start; output_use_end_handler output_use_end; output_use_ref_handler output_use_ref; output_between_use_refs_handler output_between_use_refs; output_begin_decls_handler output_begin_decls; output_end_decls_handler output_end_decls; output_decl_handler output_decl; output_after_xrefs_handler output_after_xrefs; } modes[] = { { "latex", &latex_mode_init, &latex_begin_macro, &latex_end_macro, &latex_finalend_chunk, &latex_output_macro_use, &latex_output_linenum, &latex_output_char, &latex_output_begin_uses, &latex_output_end_uses, &latex_output_use_start, &latex_output_use_end, &latex_output_use_ref, &latex_output_between_use_refs, &latex_output_begin_decls, &latex_output_end_decls, &latex_output_decl, &latex_output_after_xrefs } }; static const num_modes = sizeof(modes) / sizeof(struct s_modes); @endcode \begin{twproc}{set\_mode} \Description Sets the output mode. \SideEffects The left side of every assignment in the routine is a side-effect. \Inputs \begin{twparmlist} \item[lpwd] Holds ``global'' data for the program \item[mode] The mode to set. \end{twparmlist} \StartCode @code void set_mode(t_lpweevildat *lpwd, struct s_modes *mode) { lpwd->begin_macro = mode->begin_macro; lpwd->end_macro = mode->end_macro; lpwd->finalend_chunk = mode->finalend_chunk; lpwd->output_macro_use = mode->output_macro_use; lpwd->output_linenum = mode->output_linenum; lpwd->output_char = mode->output_char; lpwd->output_begin_uses = mode->output_begin_uses; lpwd->output_end_uses = mode->output_end_uses; lpwd->output_use_start = mode->output_use_start; lpwd->output_use_end = mode->output_use_end; lpwd->output_use_ref = mode->output_use_ref; lpwd->output_between_use_refs = mode->output_between_use_refs; lpwd->output_begin_decls = mode->output_begin_decls; lpwd->output_end_decls = mode->output_end_decls; lpwd->output_decl = mode->output_decl; lpwd->output_after_xrefs = mode->output_after_xrefs; } @endcode \end{twproc} \begin{twproc}{process\_args} \Description Actually handles the high-level processing of arguments. It scans for each argument and handles each one individually. It will return after the last argument. \Inputs \begin{twparmlist} \item[lpwd] Holds ``global'' data for the program \item[argc] The argument count \item[argv] The argument data \end{twparmlist} \Outputs \begin{twparmlist} \item[argidx] The index of the first argument after the option arguments. \end{twparmlist} \StartCode @code void process_args(t_lpweevildat *lpwd, int argc, char *argv[], int *argidx) { char *str; int i; int my_mode; my_mode = 0; *argidx = 1; while (argv[*argidx][0] == '-') { /* This forces the end of option processing. */ if (strcmp(argv[*argidx], "--") == 0) { (*argidx)++; break; } @ @ @ else { fprintf(stderr, "%s: Error: Invalid option specified: %s\n", argv[0], argv[*argidx]); exit(1); } (*argidx)++; } set_mode(lpwd, &(modes[i])); modes[i].init(lpwd); } @endcode The following code get a language option and tries to find it in the language list. If it is successful, it will set the language handlers to the ones for the language. If an error occurs, it will print an error and exit. @code else if (strncmp(argv[*argidx], "-lang", 5) == 0) { if (lpwd->handle_char != NULL) { fprintf(stderr, "%s: Error: only 1 -lang is allowed\n", argv[0]); exit(1); } str = get_arg_str(argidx, argc, argv, 5); if (str == NULL) { fprintf(stderr, "%s: Error: -lang specified but no lang given\n", argv[0]); exit(1); } for (i=0; ihandle_char = langs[i].handler; langs[i].init(lpwd); break; } } if (lpwd->handle_char == NULL) { fprintf(stderr, "%s: Error: Invalid lang given: %s\n", argv[0], str); exit(1); } } @endcode The following handles an output mode. It scans the mode list for the output mode. If it finds it, it sets the mode handlers. If it doesn't, it prints an error and returns. @code else if (strncmp(argv[*argidx], "-mode", 5) == 0) { if (lpwd->begin_macro != NULL) { fprintf(stderr, "%s: Error: only 1 -mode is allowed\n", argv[0]); exit(1); } str = get_arg_str(argidx, argc, argv, 5); if (str == NULL) { fprintf(stderr, "%s: Error: -mode specified but no mode given\n", argv[0]); exit(1); } for (i=0; ihandle_char == NULL) { fprintf(stderr, "%s: Error: Invalid mode given: %s\n", argv[0], str); exit(1); } } @endcode \end{twproc} @code else if (strncmp(argv[*argidx], "-I", 2) == 0) { xreflist *newinclude; xreflist *includelist; str = get_arg_str(argidx, argc, argv, 2); if (str == NULL) { fprintf(stderr, "%s: Error: -I specified but no include directory given\n", argv[0]); exit(1); } newinclude = malloc(sizeof(*newinclude)); if (newinclude == NULL) { fprintf(stderr, "%s: Error: Out of memory\n", argv[0]); exit(1); } newinclude->name = str; newinclude->next = NULL; includelist = lpwd->includes; if (includelist == NULL) { lpwd->includes = newinclude; } else { while (includelist->next != NULL) { includelist = includelist->next; } includelist->next = newinclude; } } @endcode \begin{twproc}{main} \Description The main routine. This routine initializes some data, processes the input options, then crunches on input lines. \ReturnValues \verb|int| - system return value. \Inputs \begin{twparmlist} \item[argc] The number of arguments given to the program \item[argv] The actual arguments. \end{twparmlist} \StartCode @code
int main(int argc, char *argv[]) { t_lpweevildat *lpwd; int argidx; int len; char *str; char fname[MAX_FILENAME_LENGTH+1]; @
process_args(lpwd, argc, argv, &argidx); if (lpwd->handle_char == NULL) { fprintf(stderr, "%s: No lang specified\n", argv[0]); exit(1); } if (lpwd->begin_macro == NULL) { /* No mode specified, default to the first one. */ set_mode(lpwd, &(modes[0])); } if (argc <= argidx) { fprintf(stderr, "%s: No filename specified\n", argv[0]); exit(1); } @ @ if (lpwd->codestate == IN_CODE) { fprintf(stderr, "Warning: File %s ended while outputting code", lpwd->curr_filename); lpwd->codestate = NOT_IN_CODE; lpwd->retcode = 2; } return(lpwd->retcode); } @endcode Allocate an initialize the global data structure. @code
lpwd = malloc(sizeof(*lpwd)); if (lpwd == NULL) { fprintf(stderr, "Unable to allocate enough memory\n"); exit(1); } lpwd->maxlinesize = MAXLINESIZE; lpwd->curr_lineno = 0; lpwd->code_lineno = 1; lpwd->outfile = stdout; lpwd->pounddefs = NULL; lpwd->vardefs = NULL; lpwd->uses = NULL; lpwd->begin_macro = NULL; lpwd->handle_char = NULL; lpwd->includes = NULL; lpwd->retcode = 0; @endcode Open an input file. Also, get the prefix (everything before the last period in the name), generate a cross reference filename, and input the cross reference file. @code lpwd->infile = fopen(argv[argidx], "r"); while ((argidx < argc) && (lpwd->infile == NULL)) { fprintf(stderr, "%s: Unable to open file %s\n", argv[0], argv[argidx]); argidx++; lpwd->infile = fopen(argv[argidx], "r"); } lpwd->curr_filename = argv[argidx]; str = strrchr(argv[argidx], '.'); if (str != NULL) { len = str - argv[argidx]; } else { len = strlen(argv[argidx]); } if ((len+4) >= MAX_FILENAME_LENGTH) /* Add 4 for the .xfr */ { fprintf(stderr, "filename to long, no xref file input\n"); } else { memcpy(fname, argv[argidx], len); strcpy(&(fname[len]), ".xfr"); input_xreffile(lpwd, fname, MAIN_TYPE); } @endcode Get the input file a line at a time and process it. The program toggles between two input states. When \verb|IN_CODE|, it is handling stuff between \verb|@code| and \verb|@endcode|. Otherwise, it is processing documentation. @code lpwd->codestate = NOT_IN_CODE; while (get_input_line(lpwd) == GOT_LINE) { if (lpwd->codestate == IN_CODE) { if (strncmp(lpwd->line, "@endcode", 8) == 0) { lpwd->end_macro(lpwd); lpwd->codestate = NOT_IN_CODE; output_xref(lpwd); lpwd->finalend_chunk(lpwd); } else if ( (strncmp(lpwd->line, "@staticdecls", 12) == 0) || (strncmp(lpwd->line, "@defines", 8) == 0) || (strncmp(lpwd->line, "@externdecls", 12) == 0) || (strncmp(lpwd->line, "@uses", 5) == 0)) { /* These are ignored */ } else { process_codeline(lpwd); } } else if (lpwd->line[0] == '@') { process_cmd(lpwd); } else { fputs(lpwd->line, lpwd->outfile); fputc('\n', lpwd->outfile); } } @endcode \end{twproc}