home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume3 / texchk / part1 / texchk.c < prev    next >
Encoding:
C/C++ Source or Header  |  1986-11-30  |  16.3 KB  |  737 lines

  1. /* Texchk -- a LaTeX syntax and spelling checker.
  2.    Written by JP Massar, Thinking Machines Corporation, Cambridge, MA
  3.    This code is hereby released into the public domain, for better or worse.
  4. */
  5.  
  6. #include <stdio.h>
  7. #include <ctype.h>
  8.  
  9. /* if your system doesn't have either string.h or strings.h you */
  10. /* may have to declare the string functions yourself */
  11. #ifdef BSD42
  12. #include <strings.h>
  13. #else
  14. #include <string.h>
  15. #endif
  16.  
  17. #ifdef TMC
  18. #include <ctools.h>
  19. #else
  20. #include "ctools.h"
  21. #endif
  22.  
  23. #include "texchk.h"
  24. #include "cmds.h"
  25. #include "texchars.h"
  26.  
  27. Bool Verbose_Mode = F;                  /* -v option */
  28. Bool Check_Mode = F;                    /* -c option */
  29.  
  30. int Indent_Level = 0;                   /* for verbose output mode */
  31.  
  32. Stack_Entry Lex_Stack[MAX_ENTRIES];     /* environment stack */
  33. int Lex_TOS = -1;
  34.  
  35. FILE *fp;                               /* file being processed */
  36. Bool Already_At_Eof = F;
  37.  
  38. long Current_Line = 0;                  /* where we are in input text */
  39. long Current_Char = 0;                  /* where we are in input text */
  40. long Line_Length = 0;                   /* current line length */
  41. char Line_Buffer[MAXLL];                /* buffer for input text */
  42.  
  43. Bool In_Math_Mode = F;
  44. int Math_Mode_Depth = 0;
  45.  
  46. char Keyword_Buffer[MAX_KEYWORD_LENGTH];
  47.  
  48.  
  49. new_file ()
  50. {
  51.   Current_Line = 0;
  52.   Current_Char = 0;
  53.   Line_Length = 0;
  54.   In_Math_Mode = F;
  55.   Math_Mode_Depth = 0;
  56.   Indent_Level = 0;
  57. }  
  58.         
  59. do_indent (level) int level;
  60. {
  61.   int j,i;
  62.   for (j = 0; j < level; j++)
  63.       for (i = 0; i < SPACES_PER_INDENT_LEVEL; i++) putc(' ',stderr);
  64. }
  65.  
  66.  
  67. lex_push (etype,keyword,linenum) envtype etype; char *keyword; long linenum;
  68.  
  69. /* push an environment onto the stack */
  70.  
  71. {
  72.   if (++Lex_TOS >= MAX_ENTRIES) {
  73.      fprintf(stderr,"Stack overflow...Process terminating.\n");
  74.      texit();
  75.   }
  76.   Lex_Stack[Lex_TOS].etype = etype;
  77.   Lex_Stack[Lex_TOS].keyword = keyword;
  78.   Lex_Stack[Lex_TOS].linenum = linenum;
  79. }
  80.  
  81.  
  82. lex_pop (ptr_etype,ptr_keyword,ptr_linenum)
  83.  
  84. /* pop an environment and return its components */
  85.  
  86.   envtype *ptr_etype;
  87.   char **ptr_keyword;
  88.   long *ptr_linenum;
  89.  
  90. {
  91.   if (Lex_TOS < 0) {
  92.      fprintf(stderr,"Stack underflow...Process terminating\n");
  93.      texit();
  94.   }
  95.   *ptr_etype = Lex_Stack[Lex_TOS].etype;
  96.   *ptr_keyword = Lex_Stack[Lex_TOS].keyword;
  97.   *ptr_linenum = Lex_Stack[Lex_TOS].linenum;
  98.   Lex_TOS--;
  99. }
  100.  
  101.  
  102. curstack (ptr_etype,ptr_keyword,ptr_linenum)
  103.  
  104.   /* get the components of the current stack entry, but leave the entry */
  105.   /* on the stack. */
  106.  
  107.   envtype *ptr_etype;
  108.   char **ptr_keyword;
  109.   long *ptr_linenum;
  110.  
  111. {
  112.   if (Stack_Empty) {
  113.      fprintf(stderr,"Fatal error, bad call to curstack\n");
  114.      texit();
  115.   }
  116.   lex_pop(ptr_etype,ptr_keyword,ptr_linenum);
  117.   lex_push(*ptr_etype,*ptr_keyword,*ptr_linenum);
  118. }
  119.  
  120.  
  121. char *copy_keyword (starttoken,endtoken) int starttoken,endtoken;
  122.  
  123. /* grab a keyword from the Line_Buffer and copy it into a static buffer */
  124.  
  125. {
  126.   int len;
  127.   if (MAX_KEYWORD_LENGTH <= (len = (endtoken - starttoken) + 1)) {
  128.      keyword_length_error();
  129.      texit();
  130.   }
  131.   strncpy(Keyword_Buffer,Line_Buffer + starttoken,len);
  132.   Keyword_Buffer[len] = '\0';
  133.   return(Keyword_Buffer);
  134. }
  135.  
  136.  
  137. do_pop (etype,keyword) envtype etype; char *keyword;
  138.  
  139. /* make sure that the current environment is the matching begin-environment */
  140. /* for the end-environment that we have just discovered.  If so, pop the */
  141. /* environment off the stack.   If not its an error. */
  142.  
  143. {
  144.         
  145.   envtype oldetype;
  146.   char *oldkeyword;
  147.   long oldlinenum;
  148.   char *s, *e;
  149.         
  150.   lex_pop(&oldetype,&oldkeyword,&oldlinenum);
  151.         
  152.   switch (etype) {
  153.      
  154.     case ESCAPE_END :
  155.       s = "\\begin";
  156.       e = "\\end";
  157.       if (oldetype != ESCAPE_BEGIN) goto nesterror;
  158.       if (0 != strcmp(oldkeyword,keyword)) goto nesterror;
  159.       break;
  160.  
  161.     case RIGHT_SQUARE_BRACKET :
  162.       s = "[";
  163.       e = "]";
  164.       if (oldetype != LEFT_SQUARE_BRACKET) goto nesterror;
  165.       break;
  166.  
  167.     case RIGHT_CURLY_BRACKET :
  168.       s = "{";
  169.       e = "}";
  170.       if (oldetype != LEFT_CURLY_BRACKET) goto nesterror;
  171.       break;
  172.  
  173.     case MATH :
  174.       s = "Begin Math Mode";
  175.       e = "End Math Mode";
  176.       if (oldetype != etype) goto nesterror;
  177.       break;
  178.       
  179.     case DOUBLE_MATH :
  180.       s = "Begin Display Math Mode";
  181.       e = "End Display Math Mode";
  182.       if (oldetype != etype) goto nesterror;
  183.       break;
  184.  
  185.   }
  186.  
  187.   return(0);
  188.  
  189.   nesterror:
  190.  
  191.   nest_error(s,e,oldlinenum,oldkeyword);
  192.   texit();
  193.  
  194. }
  195.  
  196.  
  197. int get_a_char ()
  198.  
  199. /* buffered input routine, to keep track of line number */
  200.  
  201. {
  202.   int ch,rval;
  203.   if (Current_Char >= Line_Length) {
  204.      switch (rval = getline(fp,Line_Buffer,MAXLL-2)) {
  205.        case AT_EOF:
  206.          return(EOF);
  207.          break;
  208.        case TOO_MANY_CHARS :
  209.          line_too_long_error();
  210.          texit();
  211.          break;
  212.        default :
  213.          Line_Buffer[rval] = '\n';
  214.          Line_Buffer[++rval] = '\0';
  215.          Line_Length = rval;
  216.          Current_Char = 0;
  217.          Current_Line++;
  218.          break;
  219.      }
  220.   }
  221.   ch = (int) (255 & Line_Buffer[Current_Char++]);
  222.   if (!LGL_CHAR(ch)) bad_char_error(ch,T);
  223.   return(ch);
  224. }  
  225.   
  226.  
  227. unget_a_char ()
  228.  
  229. {
  230.   if (Current_Char == 0) {        
  231.      fprintf(stderr,"Invalid unget...process terminating\n");
  232.      texit();
  233.   }
  234.   Current_Char--;
  235. }  
  236.  
  237.  
  238. char *get_keyword ()
  239.  
  240. /* read a keyword.  Keywords consist of contiguous alphabetic characters */
  241. /* keyword returned is in a static buffer. */
  242.  
  243. {
  244.   int starttoken,endtoken,ch;
  245.   starttoken = Current_Char - 1;
  246.   endtoken = Current_Char - 1;
  247.   while (isalpha(ch = get_a_char())) {
  248.     endtoken++;
  249.   }
  250.   if (ch == EOF) {
  251.      Already_At_Eof = 1;
  252.   }
  253.   else unget_a_char();
  254.   return(copy_keyword(starttoken,endtoken));
  255.  
  256.  
  257. char *get_begin_end_keyword ()
  258.  
  259. /* called after a \begin or \end construct is found. */
  260. /* begin and end keywords are enclosed in {}. */
  261. /* a warning is issued if there is any whitespace within the {}s */
  262. /* returns a string constituting what is in between the {}s save for */
  263. /* whitespace immediately after the { and immediately before the } */
  264.  
  265. /* keyword returned is in a static buffer. */
  266.  
  267. {
  268.   int ch;        
  269.   int starttoken,endtoken;
  270.   
  271.   ch = get_a_char();
  272.   if (ch != LCB) {
  273.      no_brace_after_begin_end_error();
  274.      texit();
  275.   }
  276.   
  277.   starttoken = Current_Char;
  278.   endtoken = starttoken - 1;
  279.   while (RCB != (ch = get_a_char())) {
  280.     if (ch == '\n')
  281.        warning_close_brace();
  282.     else if (ch == EOF) {
  283.        eof_error();
  284.        texit();
  285.     }
  286.     else 
  287.        endtoken++;
  288.   }
  289.   
  290.   /* ignore whitespace after '{' and before '}' */
  291.   
  292.   if (ISWHITE(Line_Buffer[starttoken]) || ISWHITE(Line_Buffer[endtoken])) {
  293.      warning_blanks_in_cb();
  294.   }
  295.   
  296.   while (starttoken < endtoken && ISWHITE(Line_Buffer[starttoken]))
  297.     starttoken++;
  298.   if (starttoken >= endtoken) {
  299.      blank_begin_end_error();
  300.      texit();
  301.   }
  302.   while (endtoken > starttoken && ISWHITE(Line_Buffer[endtoken]))
  303.     endtoken--;
  304.   return(copy_keyword(starttoken,endtoken));
  305.   
  306. }  
  307.  
  308.  
  309. get_token (action,etype,keyword) 
  310.  
  311.   /* get the next significant token from the input stream. Based on its type */
  312.   /* an action to perform is computed.  The significant part of the token is */
  313.   /* returns in *keyword, which points to a static buffer. */
  314.  
  315.   /* returns 0 on encountering EOF, otherwise returns 1. */
  316.  
  317.   Actions *action;
  318.   envtype *etype;
  319.   char **keyword;
  320.  
  321. {  
  322.   int ch,isbegin,isend;
  323.   
  324.   *keyword = 0;
  325.   if (Already_At_Eof) return(0);
  326.   
  327.   readloop:
  328.   
  329.   if (EOF == (ch = get_a_char())) return(0);
  330.  
  331.   switch (ch) {
  332.  
  333.     case LSB :
  334.       *etype = LEFT_SQUARE_BRACKET;
  335.       *action = PUSH;
  336.       *keyword = "[";
  337.       return(1);
  338.  
  339.     case RSB :
  340.       *etype = RIGHT_SQUARE_BRACKET;
  341.       *action = POP;
  342.       *keyword = "]";
  343.       return(1);
  344.  
  345.     case LCB :
  346.       *etype = LEFT_CURLY_BRACKET;
  347.       *action = PUSH;
  348.       *keyword = "{";
  349.       return(1);
  350.  
  351.     case RCB :
  352.       *etype = RIGHT_CURLY_BRACKET;
  353.       *action = POP;
  354.       *keyword = "}";
  355.       return(1);
  356.  
  357.     case MATH_CHAR :
  358.     
  359.       /* Is the next character also a '$'? If so this is 'Display Math Mode' */
  360.     
  361.       if (EOF == (ch = get_a_char())) {
  362.          *action = DOLLAR;
  363.          *etype = MATH;
  364.          *keyword = "$";
  365.          Already_At_Eof = 1;
  366.       }
  367.       else if (ch == MATH_CHAR) {
  368.          *action = DOLLAR_DOLLAR;
  369.          *etype = DOUBLE_MATH;
  370.          *keyword = "$$";
  371.       }
  372.       else {
  373.          unget_a_char();
  374.          *action = DOLLAR;
  375.          *etype = MATH;
  376.          *keyword = "$";
  377.       }
  378.       return(1);
  379.  
  380.     case ESCAPE :
  381.     
  382.       /* treat specially \begin and \end */
  383.     
  384.       if (EOF == (ch = get_a_char())) {
  385.          eof_error();
  386.          texit();
  387.       }
  388.       
  389.       /* first check for single character non-alphabetic commands */
  390.       
  391.       if (!isalpha(ch)) {
  392.          *action = CHECK_SINGLE;
  393.          *etype = ESCAPE_SINGLE_CHAR;
  394.          Keyword_Buffer[0] = ch;
  395.          Keyword_Buffer[1] = '\0';
  396.          *keyword = Keyword_Buffer;
  397.          return(1);
  398.       }
  399.         
  400.         
  401.       *keyword = get_keyword();
  402.       isbegin = (0 == strcmp(*keyword,BEGINSTRING));
  403.       isend = (0 == strcmp(*keyword,ENDSTRING));
  404.       if (!isbegin && !isend) {
  405.          *action = CHECK;
  406.          *etype = ESCAPE_ANY;
  407.          return(1);
  408.       }
  409.       
  410.       *etype = isbegin ? ESCAPE_BEGIN : ESCAPE_END;
  411.       *action = isbegin ? PUSH : POP;
  412.       *keyword = get_begin_end_keyword();
  413.       return(1);
  414.  
  415.     case COMMENT :
  416.     
  417.       /* just read in the rest of the line and ignore what's on it */
  418.     
  419.       while ('\n' != (ch = get_a_char())) {
  420.         if (EOF == ch) return(0);
  421.       }
  422.       goto readloop;
  423.     
  424.     default :
  425.       goto readloop;
  426.       
  427.   }
  428.  
  429. }
  430.  
  431. push_math_mode (key) char *key;
  432. {
  433.   if (Verbose_Mode) {
  434.      do_indent(Indent_Level++);
  435.      fprintf (
  436.          stderr,"Line %d: Entering math mode using <%s>\n",Current_Line,key
  437.        );
  438.   }
  439.   Math_Mode_Depth++;
  440.   In_Math_Mode = T;
  441.   lex_push(MATH,key,Current_Line);
  442. }
  443.  
  444. pop_math_mode (key) char *key;
  445. {
  446.   envtype etype;
  447.   char *keyword;
  448.   long linenum;
  449.   if (Verbose_Mode) {
  450.      do_indent(--Indent_Level);
  451.      fprintf (
  452.          stderr,"Line %d: Leaving math mode using <%s>\n",Current_Line,key
  453.        );
  454.   }
  455.   Math_Mode_Depth--;
  456.   In_Math_Mode = (Math_Mode_Depth > 0);
  457.   lex_pop(&etype,&keyword,&linenum);
  458. }
  459.  
  460.  
  461. math_mode_action (action,keyword) Actions action; char *keyword;
  462.  
  463. /* check for math mode tokens, and enter or leave math mode as appropriate */
  464.  
  465. {
  466.   char *stack_keyword;
  467.   long linenum;
  468.   envtype etype;
  469.   char *key, *matching_keyword;
  470.   
  471.   switch (action) {
  472.  
  473.     /* If there is a matching '$' or '$$' as the latest entry on the stack */
  474.     /* we pop it because it is a matching token.  Otherwise, we push it, */
  475.     /* even if we are already in math mode. */
  476.         
  477.     case (DOLLAR) :
  478.     case (DOLLAR_DOLLAR) :
  479.       key = (action == DOLLAR) ? "$" : "$$";
  480.       if (!In_Math_Mode) {
  481.          push_math_mode(key);
  482.          break;
  483.       }
  484.       curstack(&etype,&stack_keyword,&linenum);
  485.       if (0 != strcmp(key,stack_keyword)) {
  486.          push_math_mode(key);
  487.       }
  488.       else {
  489.          pop_math_mode(key);
  490.       }
  491.       break;
  492.      
  493.     /* just adjust Math Mode for PUSH and POP, because in process_file */
  494.     /* we will do the actual pushing and popping of these environments. */
  495.       
  496.     case (PUSH) :
  497.       if (is_math_environment(keyword)) {
  498.          Math_Mode_Depth++;
  499.          In_Math_Mode = T;
  500.       }
  501.       break;
  502.  
  503.     case (POP) :
  504.       if (is_math_environment(keyword)) {
  505.          Math_Mode_Depth--;
  506.          In_Math_Mode = (Math_Mode_Depth == 0);
  507.       }
  508.       break;
  509.       
  510.     /* look for \( and \[ commands which put us into math mode, and \) and */
  511.     /* \] commands which pop us out of math mode.   Make sure if we are */
  512.     /* popping that the proper pushed math mode command is the current */
  513.     /* stack entry. */
  514.       
  515.     case (CHECK_SINGLE) :
  516.       if (*keyword == '(' || *keyword == '[') {
  517.          push_math_mode(anewstr(keyword));
  518.       }
  519.       else if (*keyword == ')' || *keyword == ']') {
  520.          if (Stack_Empty) {
  521.             stack_empty_error(MATH,keyword);
  522.             texit();
  523.          }
  524.          curstack(&etype,&stack_keyword,&linenum);
  525.          matching_keyword = (*keyword == ')') ? "(" : "[";
  526.          if (0 != strcmp(matching_keyword,stack_keyword)) {
  527.             nest_error(matching_keyword,keyword,linenum,stack_keyword);
  528.             texit();
  529.          }
  530.          pop_math_mode(keyword);
  531.       }
  532.       break;
  533.  
  534.   }
  535.  
  536. }
  537.  
  538.  
  539. process_file () 
  540.  
  541. /* Get significant LaTeX forms from the input file.  For each one, depending */
  542. /* on its nature perform a verification or manipulate the environment stack. */
  543. /* When we are done the stack should be empty. */
  544.  
  545. /* The file has already been opened using the global file descriptor 'fp' */
  546.  
  547. {
  548.   Actions action;          
  549.   envtype etype;
  550.   char *keyword;
  551.   int cmd_index,ch;
  552.         
  553.   while (0 != get_token(&action,&etype,&keyword)) {
  554.  
  555.     switch (action) {
  556.  
  557.       case (POP) :
  558.  
  559.         /* \end{keyword},, '}', ']' */
  560.       
  561.         if (Stack_Empty) {
  562.            stack_empty_error(etype,keyword);
  563.            texit();
  564.         }
  565.         
  566.         math_mode_action(POP,keyword);
  567.       
  568.         if (Verbose_Mode && *keyword != '}' && *keyword != ']') {
  569.            do_indent(--Indent_Level);
  570.            printf("line %d: \\end{%s}\n",Current_Line,keyword);
  571.         }
  572.         do_pop(etype,keyword);
  573.         break;
  574.  
  575.       case (PUSH) :
  576.       
  577.         /* \begin{keyword}, '{', '[' */
  578.       
  579.         math_mode_action(PUSH,keyword);
  580.       
  581.         if (Verbose_Mode && *keyword != '{' && *keyword != '[') {
  582.            do_indent(Indent_Level++);
  583.            printf("line %d: \\begin{%s}\n",Current_Line,keyword);
  584.         }
  585.         
  586.         if (0==strcmp("verbatim",keyword) || 0==strcmp("verbatim*",keyword)) {
  587.            do_verbatim(keyword);
  588.            break;
  589.         }
  590.         else {
  591.            lex_push(etype,anewstr(keyword),Current_Line);
  592.            break;
  593.         }
  594.  
  595.       case (DOLLAR) :
  596.         math_mode_action(DOLLAR,keyword);
  597.         break;
  598.         
  599.       case (DOLLAR_DOLLAR) :
  600.         math_mode_action(DOLLAR_DOLLAR,keyword);
  601.         break;
  602.       
  603.       case (CHECK_SINGLE) :
  604.  
  605.         /* check for \(, \[, \), \] for math mode */
  606.       
  607.         math_mode_action(CHECK_SINGLE,keyword);
  608.         
  609.         if (Check_Mode) {
  610.            if (!LGL_SINGLE_COMMAND_CHAR(*keyword)) {
  611.               single_char_command_error(*keyword);
  612.            }
  613.            if (NOT_FOUND == (cmd_index = command_lookup(keyword))) {
  614.               fprintf(stderr,"Fatal error:\n");
  615.               fprintf(stderr,"Command Table and Legal Chars out of sync\n");
  616.               exit(1);
  617.            }
  618.            if (!In_Math_Mode && IS_MATH_MODE(cmd_index)) {
  619.               math_keyword_error(keyword);
  620.            }
  621.               
  622.         }
  623.         break;
  624.         
  625.       case (CHECK) :
  626.       
  627.         /* \command token */
  628.       
  629.         if (0 == strcmp("verb",keyword)) {
  630.            if ('*' != (ch = get_a_char())) unget_a_char();
  631.            do_verb();
  632.            break;
  633.         }
  634.       
  635.         if (Check_Mode) {
  636.            if (NOT_FOUND == (cmd_index = command_lookup(keyword))) {
  637.               keyword_error(keyword);
  638.            }
  639.            else if (!In_Math_Mode && IS_MATH_MODE(cmd_index)) {
  640.               math_keyword_error(keyword);
  641.            }
  642.         }
  643.         
  644.         break;
  645.         
  646.       default :
  647.         fprintf(stderr,"Invalid return from get_token...\n");
  648.         texit();
  649.         
  650.     }
  651.  
  652.   }
  653.     
  654.   if (!Stack_Empty) {
  655.      eof_error();
  656.      texit();
  657.   }
  658.  
  659.   return(0);
  660.  
  661. }
  662.  
  663.  
  664. texit ()
  665. {
  666.   fclose(fp);
  667.   exit(1);
  668. }
  669.  
  670.  
  671. usage () 
  672. {
  673.   fprintf(stderr,"\nUnrecognized argument to texchk\n");
  674.   fprintf(stderr,"Usage: texchk [ -v -c ] [ file1 file2 ... ]\n");
  675.   exit(1);
  676. }
  677.   
  678.   
  679. main (argc,argv) int argc; char **argv;
  680.  
  681. {
  682.   char **argptr;        
  683.   int j,input_files = 0;
  684.         
  685.   init_legal_chars();
  686.   
  687.   /* process command line arguments */
  688.   
  689.   argptr = argv;        
  690.   while (*++argptr != 0) {
  691.     if (**argptr == '-') {
  692.        if (strlen(*argptr) != 2) { 
  693.           usage();
  694.        }
  695.        switch ((*argptr)[1]) {
  696.          case 'v' :
  697.            Verbose_Mode = T;
  698.            break;
  699.          case 'c' :
  700.            Check_Mode = T;
  701.            break;
  702.          default :
  703.            usage();
  704.            break;
  705.        }
  706.        *argptr = '\0';
  707.     }
  708.     else input_files = 1;
  709.   }
  710.         
  711.   /* read and process each file */
  712.   
  713.   if (!input_files) {
  714.      printf("\n");
  715.      fp = stdin;
  716.      process_file();
  717.      printf("\nOK!\n\n");
  718.   }  
  719.   else {
  720.     for (j = 1; j < argc; j++) {
  721.         if (argv[j] != '\0') {
  722.            printf("\nChecking file %s.\n\n",argv[j]);
  723.            new_file();
  724.            fp = (FILE *) efopen(argv[j],"r");
  725.            process_file();
  726.            fclose(fp);
  727.         }
  728.     }
  729.  }
  730.   
  731.  exit(0);
  732.  
  733. }
  734.  
  735.  
  736.