%
%   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 <*>
@<Includes>
@<Defines>
@<Code>
@endcode

\subsection{Includes}
Just some standard stuff to include.  The \verb|weevinfo| file
contains all the major data structures for weevil.

@code <Includes>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

#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 <Defines>
/* 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 <Code>
@<Utils>
@<open_include_file>
@<input_xreffile>
@<process_cmd>
@<process_codeline>
@<output_xref>
@<Get Args>
@<main>
@endcode

\subsubsection{Utils}
These are various utilities that may be used both inside and outside
this module.

@code <Utils>
@<get_input_line>
@<r_strtok>
@<stralloc>
@<find_name_in_xref>
@<find_macro_in_xref>
@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 <r_strtok>
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 <stralloc>
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 <get_input_line>
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 <find_name_in_xref>
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 <find_macro_in_xref>
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 <process_cmd>
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;
   }
   @<Handle code command>
   @<Handle file command>
   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 <Handle code command>
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 <Handle file command>
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 <process_codeline>
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 <output_xref>
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 <open_include_file>
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 <input_xreffile>
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 <Get Args>
@<get_arg_str>
@<Argument Data>
@<set_mode>
@<process_args>
@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 <get_arg_str>
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 <Argument Data>
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 <set_mode>
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 <process_args>
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;
      }
      @<Handle lang option>
      @<Handle mode option>
      @<Handle include option>
      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 <Handle lang option>
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; i<num_langs; i++)
   {
      if (strcmp(str, langs[i].lang_name) == 0)
      {
         lpwd->handle_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 <Handle mode option>
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; i<num_modes; i++)
   {
      if (strcmp(str, modes[i].mode_name) == 0)
      {
         my_mode = i;
         break;
      }
   }
   
   if (lpwd->handle_char == NULL)
   {
      fprintf(stderr,
              "%s: Error: Invalid mode given: %s\n",
              argv[0], str);
      exit(1);
   }
}
@endcode
\end{twproc}

@code <Handle include option>
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 <main>
int
main(int argc,
     char *argv[])
{
   t_lpweevildat *lpwd;
   int argidx;
   int len;
   char *str;
   char fname[MAX_FILENAME_LENGTH+1];


   @<Main init>

   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);
   }

   @<Open input file>

   @<Scan input>

   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 <Main init>
   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 <Open input file>
   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 <Scan input>
   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}

