1 /*                    -*- Mode: C -*-
2  *
3  * lacheck.lex - A consistency checker checker for LaTeX documents
4  *
5  * Copyright (C) 1991, 1992 Kresten Krab Thorup.
6  * Copyright (C) 1993 --- 1998 Per Abrahamsen.
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 1, or (at your option)
11  * any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21  *
22  * Please send bugs, suggestions, and queries to auc-tex_mgr@sunsite.auc.dk.
23  *
24  * $Revision: 1.26 $
25  * Author          : Kresten Krab Thorup
26  * Created On      : Sun May 26 18:11:58 1991
27  *
28  * HISTORY
29  * 07-Mar-1998		Per Abrahamsen
30  *    Added return to yywrap.  Patch by Fabrice POPINEAU
31  *    <popineau@esemetz.ese-metz.fr>.
32  * 14-Jan-1998		Per Abrahamsen
33  *    Added GPL blurp.
34  * 27-Oct-1997		Per Abrahamsen
35  *    Count newline after newenvironment and newcommand.
36  * 12-Jan-1996          Per Abrahamsen
37  *    \\} used not to end a group in definitions.  Reported by Piet
38  *    van Oostrum <piet@cs.ruu.nl>.
39  * 03-Jan-1995		Per Abrahamsen
40  *    Fix bug which prevented detection of multiple illegal characters
41  *    in labels.  Reported by eeide@jaguar.cs.utah.edu (Eric Eide).
42  * 30-Jul-1994		Per Abrahamsen
43  *    Define dummy yywrap so we no longer depend on `libl.a'.
44  * 26-Apr-1994		Per Abrahamsen
45  *    Removed a few warnings, by Richard Lloyd <R.K.Lloyd@csc.liv.ac.uk>.
46  * 23-Apr-1994		Per Abrahamsen
47  *    Changed all `%i' to `%d' for VMS portability.  Reported by
48  *    Stephen Harker <PHS172M@vaxc.cc.monash.edu.au>.
49  * 16-Feb-1994		Per Abrahamsen
50  *    Try file name with `.tex' appended before trying it bare.  This
51  *    will make the case where a directory and a TeX file share the
52  *    same name work.
53  * 19-Jan-1994		Per Abrahamsen
54  *    Comments don't imply whitespace.  Pointed out by Jacco van
55  *    Ossenbruggen <jrvosse@cs.vu.nl>.
56  * 14-Jan-1994		Per Abrahamsen
57  *    Don't complain about \ref at the beginning of a paragraph.
58  *    Suggested by Jean-Marc Lasgouttes <Jean-Marc.Lasgouttes@inria.fr>.
59  * 11-Jan-1994		Per Abrahamsen
60  *    Added version string to usage message.  Suggested by Uwe Bonnes
61  *    <bon@LTE.E-TECHNIK.uni-erlangen.de> .
62  * 04-Jan-1994		Per Abrahamsen
63  *    Warn about newlines in \verb.  Suggested by Mark Burton
64  *    <markb@ordern.demon.co.uk>.  The LaTeX Book agrees (p. 168).
65  * 10-Sep-1993          Per Abrahamsen
66  *    Removed complain about missing ~ before \cite.  Requested by
67  *    Nelson H. F. Beebe <beebe@math.utah.edu>.  The LaTeX Book seems
68  *    to agree.
69  * 03-Sep-1993	        Per Abrahamsen
70  *    Check for illegal characters in labels.
71  * 16-Aug-1993	        Per Abrahamsen
72  *    Recognize \endinput.  Suggested by Stefan Farestam
73  *    <Stefan.Farestam@cerfacs.fr>.
74  * 13-Aug-1993          Per Abrahamsen
75  *    } was eaten after display math.  Reported by Eckhard R�ggeberg
76  *    <eckhard@ts.go.dlr.de>.
77  * 13-Aug-1993          Per Abrahamsen
78  *    Recognize \verb*.  Reported by Eckhard R�ggeberg
79  *    <eckhard@ts.go.dlr.de>.
80  * 08-Aug-1993          Per Abrahamsen
81  *    Better catch begin and end without arguments.
82  * 08-Aug-1993          Per Abrahamsen
83  *    Removed free(NULL) as reported by Darrel R. Hankerson
84  *    <hankedr@mail.auburn.edu>.
85  * 08-Aug-1993		Per Abrahamsen
86  *    Removed declaration of realloc for some C compilers.  Reported by
87  *    Darrel R. Hankerson <hankedr@mail.auburn.edu>
88  * 30-Jul-1993          Per Abrahamsen
89  *    Added check for italic correction after normal text.
90  * 29-Jul-1993          Per Abrahamsen
91  *    Added cast for (char*) malloc as suggested by John Interrante
92  *    <interran@uluru.Stanford.EDU>.
93  * 29-Jul-1993          Per Abrahamsen
94  *    Added check for missing and extra italic correction.
95  * 29-Jul-1993	        Per Abrahamsen
96  *    Made line number counting more reliable (but it still needs a rewrite)!
97  * 28-Jul-1993	        Per Abrahamsen
98  *    Added check for italic correction before point or comma.
99  * 6-Jun-1992		Kresten Krab Thorup
100  *    Last Modified: Sat Jun  6 16:37:44 1992 #48 (Kresten Krab Thorup)
101  *    Added test for whitespace before punctuation mark
102  * 17-Dec-1991  (Last Mod: Tue Dec 17 21:01:24 1991 #41)  Kresten Krab Thorup
103  *    Added 'word word` and missing ~ before cite and ref
104  * 18-Jun-1991  (Last Mod: Tue Jun 18 19:20:43 1991 #17)  Kresten Krab Thorup
105  *    Added check (or rather management) for \newenvironment and
106  *    \newcommand - as suggested by Per Abrahamsen abrham@hugin.dk
107  * 30-May-1991  (Last Mod: Thu May 30 02:22:33 1991 #15)  Kresten Krab Thorup
108  *    Added check for `$${punct}' and `{punct}$' constructions
109  * 30-May-1991  (Last Mod: Wed May 29 10:31:35 1991 #6)  Kresten Krab Thorup
110  *    Improved (dynamic) stack management from Andreas Stolcke ...
111  *                                       <stolcke@ICSI.Berkeley.EDU>
112  * 26-May-1991  Kresten Krab Thorup
113  *    Initial distribution version.
114  */
115 
116 %{
117 
118 #include <stdio.h>
119 #include <string.h>
120 /* #include <sys/param.h> */
121 
122 /* extern char *realloc(); */
123 
yywrap()124 int yywrap() { return 1; }
125 
126 #ifdef NEED_STRSTR
127 char *strstr();
128 #endif
129 
130 #define GROUP_STACK_SIZE 10
131 #define INPUT_STACK_SIZE 10
132 
133 #define PROGNAME "LaCheck"
134 
135   /* macros */
136 
137 #define CG_NAME (char *)gstack[gstackp-1].s_name
138 #define CG_TYPE gstack[gstackp-1].s_type
139 #define CG_LINE gstack[gstackp-1].s_line
140 #define CG_ITALIC gstack[gstackp-1].italic
141 #define CG_FILE gstack[gstackp-1].s_file
142 
143 char *bg_command();
144 void pop();
145 void push();
146 void linecount();
147 void g_checkend();
148 void e_checkend();
149 void f_checkend();
150 void input_file();
151 void print_bad_match();
152 int check_top_level_end();
153 
154   /* global variables */
155 
156 char returnval[100];
157 int line_count = 1;
158 int warn_count = 0;
159 char *file_name;
160 char verb_char;
161 
162   /* the group stack */
163 
164 typedef struct tex_group
165  {
166     unsigned char *s_name;
167     int s_type;
168     int s_line;
169     int italic;
170     char *s_file;
171  } tex_group;
172 
173 tex_group *gstack;
174 int gstack_size = GROUP_STACK_SIZE;
175 int gstackp = 0;
176 
177 typedef struct input_
178  {
179     YY_BUFFER_STATE stream;
180     char *name;
181     int linenum;
182  } input_;
183 
184 input_ *istack;
185 int istack_size = INPUT_STACK_SIZE;
186 int istackp = 0;
187 
188 int def_count = 0;
189 
190 %}
191 
192 %x B_ENVIRONMENT E_ENVIRONMENT VERBATIM INCLUDE MATH COMMENT VERB DEF
193 %x AFTER_DISPLAY ENV_DEF ICOR GETICOR
194 
195 b_group ("{"|\\bgroup)
196 e_group ("}"|\\egroup)
197 
198 b_math \\\(
199 e_math \\\)
200 math \$
201 
202 b_display \\\[
203 e_display \\\]
204 display \$\$
205 
206 par ([ \t]*\n[ \t]*\n[ \t\n]*)
207 non_par_ws ([ \t]+\n?[ \t]*|[ \t]*\n[ \t]*|[ \t]*\n?[ \t]+)
208 
209 ws [ \n\t](%[^\n]\n)*
210 space ({ws}|\~|\\space)
211 hard_space (\~|\\space)
212 
213 u_letter [A-Z���]
214 l_letter [a-z���]
215 punct [\!\.\?]
216 atoz [a-zA-Z]
217 letter [A-Z���a-z���]
218 
219 c_bin ("-"|"+"|"\\cdot"|"\\oplus"|"\\otimes"|"\\times")
220 l_bin (",")
221 
222 general_abbrev {letter}+{punct}
223 
224 non_abbrev {u_letter}{u_letter}+{punct}
225 
226 italic_spec (sl|it)
227 normal_spec normalshape
228 swap_spec em
229 font_spec (rm|bf|{italic_spec}|tt|{swap_spec}|mediumseries|{normal_spec})
230 
231 primitive \\(above|advance|catcode|chardef|closein|closeout|copy|count|countdef|cr|crcr|csname|delcode|dimendef|dimen|divide|expandafter|font|hskp|vskip|openout)
232 
233 symbol ("$"("\\"{atoz}+|.)"$"|"\\#"|"\\$"|"\\%"|"\\ref")
234 
235 %%
236 
237 <*>"\\\\" { ; }
238 
239 <ENV_DEF,DEF,INITIAL>"\\\%" { ; }
240 
241 <ICOR,GETICOR,ENV_DEF,DEF,INITIAL>"%"[^\n]*\n { line_count++; }
242 
243 <ICOR,GETICOR,ENV_DEF,DEF,INITIAL>\n 	{ line_count++; }
244 
245 <ENV_DEF,DEF,INITIAL>"\\\{" { ; }
246 
247 <ENV_DEF,DEF,INITIAL>"\\\}" { ; }
248 
249 "\\\$" { ; }
250 
251 <ICOR,INITIAL,GETICOR>"{"{ws} {
252   if (CG_TYPE != 4 && CG_TYPE != 5) {
253     if (!(CG_TYPE == 2 && strstr(CG_NAME, "array"))) {
254       printf( "\"%s\", line %d: possible unwanted space at \"{\"\n",
255 	     file_name, line_count);
256       ++warn_count ;
257     }
258   }
259   push( "{", 0, line_count);
260   linecount();
261  }
262 
263 <ICOR,INITIAL,GETICOR>{b_group} {  push( "{", 0, line_count);}
264 
265 <INITIAL,GETICOR>{e_group} {
266   {
267     int italic = CG_ITALIC;
268     g_checkend(0);
269     if (italic && !CG_ITALIC)
270       BEGIN(GETICOR) ;
271     else
272       BEGIN(INITIAL);
273   }}
274 
275 <ICOR>{e_group} {  g_checkend(0); }
276 
277 <GETICOR>[A-Za-z������0-9;:!()]+ {
278  {
279    if (!CG_ITALIC)
280      {
281        printf("\"%s\", line %d: you may need a \\/ before \"%s\"\n",
282 	      file_name, line_count, yytext);
283        ++warn_count;
284      }
285     BEGIN(INITIAL);
286  }}
287 
288 <ICOR>[A-Za-z������0-9;:!?()`']+ {
289  {
290    if (CG_ITALIC)
291      {
292        printf("\"%s\", line %d: \\/ not needed before italic text \"%s\"\n",
293 	      file_name, line_count, yytext);
294        ++warn_count;
295      }
296     BEGIN(INITIAL);
297  }}
298 
299 ^[A-Za-z������0-9;:!?()`',.]+{ws}*/\\\/ {
300   {
301    linecount();
302    if (!CG_ITALIC)
303      {
304        printf("\"%s\", line %d: \\/ not needed after non-italic text \"%s\"\n",
305               file_name, line_count, yytext);
306        ++warn_count;
307      }
308  }}
309 
310 {ws}[A-Za-z������0-9;:!?()`',.]+{ws}*/\\\/ {
311   {
312    linecount();
313    if (!CG_ITALIC)
314      {
315        printf("\"%s\", line %d: \\/ is not needed after non-italic \"%s\"\n",
316               file_name, line_count, yytext);
317        ++warn_count;
318      }
319  }}
320 
321 <GETICOR>\\\/ { BEGIN(INITIAL); }
322 
323 <INITIAL>\\\/ { BEGIN(ICOR); }
324 
325 <ICOR>\\\/ {
326   {
327     printf("\"%s\", line %d: double \\/ found \"%s\"\n",
328            file_name, line_count, yytext);
329     ++warn_count;
330     BEGIN(ICOR);
331   }}
332 
333 <INITIAL,GETICOR,ICOR>\\{italic_spec}/[^a-zA-Z] { CG_ITALIC = 1; }
334 
335 <INITIAL,GETICOR>\\{normal_spec}/[^a-zA-Z] {
336   {
337     if(CG_ITALIC)
338       BEGIN(GETICOR);
339     else
340       BEGIN(INITIAL);
341     CG_ITALIC = 0;
342   }}
343 
344 <ICOR>\\{normal_spec}/[^a-zA-Z] { CG_ITALIC = 0; }
345 
346 <INITIAL,GETICOR>\\{swap_spec}/[^a-zA-Z] {
347   {
348     if(CG_ITALIC)
349       BEGIN(GETICOR);
350     else
351       BEGIN(INITIAL);
352     CG_ITALIC = !CG_ITALIC;
353   }}
354 
355 <ICOR>\\{swap_spec}/[^a-zA-Z] { CG_ITALIC = !CG_ITALIC; }
356 
357 <ICOR>[,.] {
358  {
359     printf("\"%s\", line %d: do not use \\/ before \"%s\"\n",
360 	   file_name, line_count, yytext);
361     ++warn_count;
362     BEGIN(INITIAL);
363  }}
364 
365 <GETICOR,ICOR>{ws} { ; }
366 
367 <GETICOR,ICOR>~ { ; }
368 
369 <GETICOR,ICOR>[^\n] {
370   {
371     unput(yytext[0]);
372     BEGIN(INITIAL);
373   }}
374 
375 "\\"[exg]?(def|newcommand)[^\n\{]+ 	BEGIN(DEF);
376 
377 <DEF>{b_group} { ++def_count; }
378 
379 <DEF>{e_group} { --def_count;
380 		 if(def_count == 0)
381 		     BEGIN(INITIAL); }
382 
383 <DEF>. { ; }
384 
385 "\\"newenvironment"{"[a-zA-Z]+"}"[^\n\{]+ 	BEGIN(ENV_DEF);
386 
387 <ENV_DEF>{b_group} { ++def_count; }
388 
389 <ENV_DEF>{e_group} { --def_count;
390 		 if(def_count == 0)
391 		     BEGIN(DEF); }
392 
393 <ENV_DEF>. { ; }
394 
395 {b_math} {
396     if(CG_TYPE == 4 || CG_TYPE == 5)
397 	print_bad_match(yytext,4);
398     else
399     {
400 	push( yytext, 4, line_count);
401     }}
402 
403 {e_math} {  g_checkend(4); }
404 
405 {b_display} {
406     if(CG_TYPE == 4 || CG_TYPE == 5)
407 	print_bad_match(yytext,5);
408     else
409     {
410 	push( yytext, 5, line_count);
411     }}
412 
413 
414 {e_display} {  g_checkend(5);     BEGIN(AFTER_DISPLAY);}
415 
416 <AFTER_DISPLAY>{punct} {
417 
418     printf( "\"%s\", line %d: punctuation mark \"%s\" should be placed before end of displaymath\n",
419 	   file_name, line_count, yytext);
420     ++warn_count ;
421 
422   BEGIN(INITIAL); }
423 
424 <AFTER_DISPLAY>(\n|.) { unput(yytext[0]); BEGIN(INITIAL); }
425 
426 <ICOR,INITIAL,GETICOR>{punct}/("\$"|"\\)") { if (CG_TYPE == 4)
427        {
428 	 printf( "\"%s\", line %d: punctuation mark \"%s\" should be placed after end of math mode\n",
429 		file_name, line_count, yytext);
430 	 ++warn_count ;
431 	 BEGIN(INITIAL);
432        }}
433 
434 {math} {
435 
436     if(CG_TYPE == 5)
437 	print_bad_match(yytext, 4);
438     else
439 
440     if(CG_TYPE == 4)
441     {
442 	e_checkend(4, yytext);
443     }
444     else
445     {
446 	push( yytext, 4, line_count);
447     }}
448 
449 
450 {display}  {
451 
452     if(CG_TYPE == 4)
453 	print_bad_match(yytext,5);
454     else
455 
456     if(CG_TYPE == 5)
457     {
458 	e_checkend(5, yytext);
459         BEGIN(AFTER_DISPLAY);
460     }
461     else
462     {
463 	push( yytext, 5, line_count);
464     }}
465 
466 \\begingroup/[^a-zA-Z]  {
467  {
468     push((unsigned char *)"\\begingroup", 1, line_count);
469  }}
470 
471 
472 \\endgroup/[^a-zA-Z]  {
473  {
474     g_checkend(1);
475  }}
476 
477 
478 \\begin[ \t]*"{" { BEGIN(B_ENVIRONMENT); }
479 
480 \\begin[ \t]*(%[^\n]*)?/\n {
481  {
482 
483     printf("\"%s\", line %d: {argument} missing for \\begin\n",
484 	   file_name, line_count) ;
485     ++warn_count;
486  }}
487 
488 <B_ENVIRONMENT>[^\}\n]+ {
489  {
490     if (strcmp( yytext, "verbatim" ) == 0 )
491 	{
492 	 input();
493 	 BEGIN(VERBATIM);
494 	}
495     else
496 	{
497     	 push(yytext, 2, line_count);
498 
499 	 if (   strcmp (yytext, "sl" ) == 0
500 	     || strcmp (yytext, "it" ) == 0)
501 	   CG_ITALIC = 1;
502 	 else if (strcmp (yytext, "normalshape") == 0)
503 	   CG_ITALIC = 0;
504 	 else if (strcmp (yytext, "em") == 0)
505 	   CG_ITALIC = !CG_ITALIC;
506 
507  	 input();
508 	 BEGIN(INITIAL);
509 	}
510  }}
511 
512 <VERBATIM>\\end[ \t]*\{verbatim\} { BEGIN(INITIAL); }
513 
514 <VERBATIM>\t {
515      printf("\"%s\", line %d: TAB character in verbatim environment\n",
516 	   file_name, line_count) ;
517     ++warn_count;
518  }
519 
520 <VERBATIM>. { ; }
521 
522 <VERBATIM>\n { ++line_count; }
523 
524 
525 <ICOR,INITIAL,GETICOR>\\verb\*?. {
526           verb_char = yytext[yyleng-1];
527 	  BEGIN(VERB);
528 	}
529 
530 <VERB>\n {
531   printf("\"%s\", line %d: \\verb should not contain end of line characters\n",
532 	 file_name, line_count) ;
533   ++line_count;
534 }
535 
536 <VERB>. {
537   if ( *yytext == verb_char )
538     BEGIN(INITIAL);
539 }
540 
541 
542 \\end[ \t]*"{" { BEGIN(E_ENVIRONMENT); }
543 
544 \\end[ \t]*(%[^\n]*)?/\n {
545  {
546     printf("\"%s\", line %d: {argument} missing for \\end\n",
547 	   file_name, line_count) ;
548     ++warn_count;
549  }}
550 
551 
552 <E_ENVIRONMENT>[^\}\n]+ {
553  {
554     e_checkend(2, yytext);
555     input();
556 
557     BEGIN(INITIAL);
558  }}
559 
560 
561 <ICOR,INITIAL,GETICOR>{ws}({letter}".")*{letter}*{l_letter}"."/{non_par_ws}+{l_letter}    {
562  {
563     linecount();
564     printf( "\"%s\", line %d: missing `\\ ' after \"%s\"\n",
565 	   file_name, line_count, ++yytext);
566     ++warn_count ;
567     BEGIN(INITIAL);
568  }}
569 
570 <ICOR,INITIAL,GETICOR>({l_letter}".")*{letter}*{l_letter}"."/{non_par_ws}+{l_letter}    {
571  {
572     printf( "\"%s\", line %d: missing `\\ ' after \"%s\"\n",
573 	   file_name, line_count, yytext);
574     ++warn_count ;
575     BEGIN(INITIAL);
576  }}
577 
578 <ICOR,INITIAL,GETICOR>{non_abbrev}/{non_par_ws}{u_letter}   {
579  {
580    linecount();
581    printf("\"%s\", line %d: missing `\\@' before `.' in \"%s\"\n",
582 	  file_name, line_count, yytext);
583    ++warn_count ;
584    BEGIN(INITIAL);
585  }}
586 
587 <ICOR,INITIAL,GETICOR>({hard_space}{space}|{space}{hard_space})  {
588 
589     printf("\"%s\", line %d: double space at \"%s\"\n",
590 	   file_name, line_count, yytext);
591     ++warn_count;
592 	linecount();
593     BEGIN(INITIAL);
594   }
595 
596 {c_bin}{ws}?(\\(\.|\,|\;|\:))*{ws}?\\ldots{ws}?(\\(\.|\,|\;|\:))*{ws}?{c_bin} {
597 	printf("\"%s\", line %d: \\ldots should be \\cdots in \"%s\"\n",
598 	   file_name, line_count, yytext);
599 	++warn_count;
600 	linecount();
601   }
602 
603 <ICOR,INITIAL,GETICOR>[^\\]{l_bin}{ws}?(\\(\.|\,|\;|\:))*{ws}?\\cdots{ws}?(\\(\.|\,|\;|\:))*{ws}?[^\\]{l_bin} {
604 	printf("\"%s\", line %d: \\cdots should be \\ldots in \"%s\"\n",
605 	   file_name, line_count, yytext);
606 	++warn_count;
607 	linecount();
608     BEGIN(INITIAL);
609   }
610 
611 {c_bin}{ws}?(\\(\.|\,|\;|\:))*{ws}?"."+{ws}?(\\(\.|\,|\;|\:))*{ws}?{c_bin} {
612 	printf("\"%s\", line %d: Dots should be \\cdots in \"%s\"\n",
613 	   file_name, line_count, yytext);
614 	++warn_count;
615 	linecount();
616   }
617 
618 <ICOR,INITIAL,GETICOR>[^\\]{l_bin}{ws}?(\\(\.|\,|\;|\:))*{ws}?"."+{ws}?(\\(\.|\,|\;|\:))*{ws}?[^\\]{l_bin} {
619 	printf("\"%s\", line %d: Dots should be \\ldots in \"%s\"\n",
620 	   file_name, line_count, yytext);
621 	++warn_count;
622 	linecount();
623     BEGIN(INITIAL);
624   }
625 
626 
627 <ICOR,INITIAL,GETICOR>\.\.\. {
628     printf("\"%s\", line %d: Dots should be ellipsis \"%s\"\n",
629 	   file_name, line_count, yytext);
630     ++warn_count;
631     BEGIN(INITIAL);
632   }
633 
634 <ICOR,INITIAL,GETICOR>\\label\{[^#$%^&*+={\~"<>\n\t }]*[#$%^&*+={\~"<>\n\t ][^%}]*\} {
635     linecount();
636     printf("\"%s\", line %d: bad character in label \"%s\", see C.10.2\n",
637            file_name, line_count, yytext);
638   }
639 
640 <ICOR,INITIAL,GETICOR>{par}"\\"ref/[^A-Za-z]  {
641     linecount();
642     BEGIN(INITIAL);
643   }
644 
645 <ICOR,INITIAL,GETICOR>{ws}"\\"ref/[^A-Za-z]  {
646     linecount();
647     printf("\"%s\", line %d: perhaps you should insert a `~' before \"%s\"\n",
648 	   file_name, line_count, ++yytext);
649     BEGIN(INITIAL);
650   }
651 
652 <ICOR,INITIAL,GETICOR>{ws}"\\"footnote/[^A-Za-z]  {
653     linecount();
654     printf("\"%s\", line %d: whitespace before footnote in \"%s\"\n",
655 	   file_name, line_count, ++yytext);
656     BEGIN(INITIAL);
657   }
658 
659 
660 {primitive}/[^a-zA-Z] {
661  {
662     printf("\"%s\", line %d: Don't use \"%s\" in LaTeX documents\n",
663 	   file_name, line_count, yytext);
664     ++warn_count ;
665  }}
666 
667 \\left{ws}*\\?. { linecount() ;}
668 \\right{ws}*\\?. {	linecount(); }
669 
670 <ICOR,INITIAL,GETICOR>[^\{]\\{font_spec}/[ \t]*"{" {
671  {
672    linecount();
673     printf("\"%s\", line %d: Fontspecifiers don't take arguments. \"%s\"\n",
674 	   file_name, line_count, yytext);
675     ++warn_count;
676   /*    (void) input(); */
677     BEGIN(INITIAL);
678  }}
679 
680 \\([a-zA-Z\@]+\@[a-zA-Z\@]*|[a-zA-Z\@]*\@[a-zA-Z\@]+) {
681  {
682     printf("\"%s\", line %d: Do not use @ in LaTeX macro names. \"%s\"\n",
683 	   file_name, line_count, yytext);
684     ++warn_count;
685  }}
686 
687 <ICOR,INITIAL,GETICOR>{ws}"'"+{letter}+ {
688  {
689    linecount();
690     printf("\"%s\", line %d: Use ` to begin quotation, not ' \"%s\"\n",
691 	   file_name, line_count, yytext);
692     ++warn_count;
693     BEGIN(INITIAL);
694  }}
695 
696 <ICOR,INITIAL,GETICOR>{letter}+"`" {
697  {
698     printf("\"%s\", line %d: Use ' to end quotation, not ` \"%s\"\n",
699 	   file_name, line_count, yytext);
700     ++warn_count;
701     BEGIN(INITIAL);
702  }}
703 
704 
705 <ICOR,INITIAL,GETICOR>{ws}+{punct} {
706  {
707     printf("\"%s\", line %d: Whitespace before punctation mark in \"%s\"\n",
708 	   file_name, line_count, yytext);
709     ++warn_count;
710 	linecount();
711     BEGIN(INITIAL);
712  }}
713 
714 "%"  { BEGIN(COMMENT); }
715 
716 <COMMENT>\n	{ BEGIN(INITIAL); ++line_count; }
717 
718 <COMMENT>.	{ ; }
719 
720 
721 \\(input|include)([ \t]|"{")	{ BEGIN(INCLUDE); }
722 
723 <INCLUDE>[^\}\n]+	{
724  {
725 	if ( strstr(yytext,".sty") == NULL )
726 	{
727 	  printf("** %s:\n", yytext);
728 	  input_file(yytext);
729 	}
730 	else
731 	{
732 		printf("\"%s\", line %d: Style file `%s\' omitted.\n",
733 			file_name,
734 			line_count,
735 			yytext);
736 		input();
737 	}
738 	BEGIN(INITIAL);
739  }}
740 
741 \\endinput/[^A-Za-z] |
742 <<EOF>> {
743 	  if (--istackp < 0)
744 		  yyterminate();
745 
746 	  else
747 		{
748 		  fclose(yyin);
749 	  	  f_checkend(file_name);
750 		  yy_switch_to_buffer(istack[istackp].stream);
751 		  free(file_name);
752 		  line_count = istack[istackp].linenum;
753 		  file_name = istack[istackp].name;
754 		  input();
755 		  BEGIN(INITIAL);
756 		}
757 
758 	}
759 
760 
761 . { ; }
762 %%
763 int main( argc, argv )
764 int argc;
765 char *argv[];
766 {
767     /* allocate initial stacks */
768     gstack = (tex_group *)malloc(gstack_size * sizeof(tex_group));
769     istack = (input_ *)malloc(istack_size * sizeof(input_));
770     if ( gstack == NULL || istack == NULL ) {
771 	fprintf(stderr, "%s: not enough memory for stacks\n", PROGNAME);
772 	exit(3);
773     }
774 
775     if(argc > 1)
776     {
777         if ( (file_name = (char*) malloc(strlen(argv[1]) + 5)) == NULL ) {
778 		fprintf(stderr, "%s: out of memory\n", PROGNAME);
779 		exit(3);
780 	}
781 
782 	strcpy(file_name, argv[1]);
783 	strcat(file_name, ".tex" );
784 
785 	if ((yyin = fopen( file_name, "r")) != NULL )
786 	{
787 	    push(file_name, 3, 1);
788 	    yylex();
789 	    f_checkend(file_name);
790 	}
791 	else {
792                  file_name[strlen(file_name) - 4] = '\0';
793 		 if ((yyin = fopen( file_name, "r")) != NULL )
794 		 {
795 		     push(file_name, 3, 1);
796 		     yylex();
797 		     f_checkend(file_name);
798 		 }
799 		 else
800 		     fprintf(stderr,
801 			     "%s: Could not open : %s\n",PROGNAME, argv[1]);
802 	     }
803     }
804     else
805     {
806 	printf("\n* %s *\n\n",PROGNAME);
807 	printf("\t...a consistency checker for LaTeX documents.\n");
808 	printf("$Id: lacheck.lex,v 1.26 1998/03/07 07:46:45 abraham Exp $\n\n");
809 
810 	printf("Usage:\n\tlacheck filename[.tex] <return>\n\n\n");
811 
812 	printf("\tFrom within Emacs:\n\n");
813 	printf("\tM-x compile RET lacheck filename[.tex] RET\n\n");
814 	printf("\tUse C-x ` to step through the messages.\n\n");
815 	printf("\n\tThe found context is displayed in \"double quotes\"\n\n");
816 	printf("Remark:\n\tAll messages are only warnings!\n\n");
817 	printf("\tYour document may be right even though LaCheck says ");
818 	printf("something else.\n\n");
819     }
820     return(0);
821 }
822 
823 #ifdef NEED_STRSTR
824 char *
825 strstr(string, substring)
826     register char *string;	/* String to search. */
827     char *substring;		/* Substring to try to find in string. */
828 {
829     register char *a, *b;
830 
831     /* First scan quickly through the two strings looking for a
832      * single-character match.  When it's found, then compare the
833      * rest of the substring.
834      */
835 
836     b = substring;
837     if (*b == 0) {
838 	return string;
839     }
840     for ( ; *string != 0; string += 1) {
841 	if (*string != *b) {
842 	    continue;
843 	}
844 	a = string;
845 	while (1) {
846 	    if (*b == 0) {
847 		return string;
848 	    }
849 	    if (*a++ != *b++) {
850 		break;
851 	    }
852 	}
853 	b = substring;
854     }
855     return (char *) 0;
856 }
857 #endif /* NEED_STRSTR */
858 
859 void push(p_name, p_type, p_line)
860 unsigned char *p_name;
861 int p_type;
862 int p_line;
863 {
864     if ( gstackp == gstack_size ) {	/* extend stack */
865 	gstack_size *= 2;
866 	gstack = (tex_group *)realloc(gstack, gstack_size * sizeof(tex_group));
867 	if ( gstack == NULL ) {
868 		fprintf(stderr, "%s: stack out of memory", PROGNAME);
869 	exit(3);
870     }
871     }
872 
873     if ( (gstack[gstackp].s_name =
874 		(unsigned char *)malloc(strlen((char *)p_name) + 1)) == NULL ||
875          (gstack[gstackp].s_file =
876 		(char *)malloc(strlen(file_name) + 1)) == NULL ) {
877 	fprintf(stderr, "%s: out of memory\n", PROGNAME);
878 	exit(3);
879     }
880 
881     strcpy((char *)gstack[gstackp].s_name,(char *)p_name);
882     gstack[gstackp].s_type = p_type;
883     gstack[gstackp].s_line = p_line;
884     gstack[gstackp].italic = (  (p_type == 4 || p_type == 5)
885 			      ? 1
886 			      : (  gstackp
887 				 ? gstack[gstackp - 1].italic
888 				 : 0));
889     strcpy(gstack[gstackp].s_file,file_name);
890     ++gstackp;
891 
892 }
893 
894 void input_file(file_nam)
895 char *file_nam;
896 {
897     char *tmp_file_name;
898     FILE *tmp_yyin;
899 
900     if ( (tmp_file_name = (char*) malloc(strlen(file_nam) + 5)) == NULL ) {
901 	fprintf(stderr, "%s: out of memory\n", PROGNAME);
902 	exit(3);
903     }
904     strcpy(tmp_file_name,file_nam);
905 
906     if (istackp == istack_size) {	/* extend stack */
907 	istack_size *= 2;
908 	istack = (input_ *)realloc(istack, istack_size * sizeof(input_));
909 	if ( istack == NULL ) {
910 		fprintf(stderr, "%s: \\input stack out of memory\n", PROGNAME);
911 	exit(3);
912         }
913     }
914 
915     istack[istackp].stream = YY_CURRENT_BUFFER;
916     istack[istackp].linenum = line_count;
917     istack[istackp].name = file_name;
918     ++istackp;
919 
920     (void) strcat(tmp_file_name, ".tex");
921     if ((tmp_yyin = fopen( tmp_file_name, "r")) != NULL )
922 	{
923 	  yyin = tmp_yyin;
924 	  yy_switch_to_buffer(yy_create_buffer(yyin,YY_BUF_SIZE));
925 	  file_name = tmp_file_name;
926 	  push(file_name, 3, 1);
927           line_count = 1;
928 	}
929     else {
930           tmp_file_name[strlen(tmp_file_name) - 4] = '\0';
931 	  if ((tmp_yyin = fopen( tmp_file_name , "r")) != NULL )
932 	    {
933 		yyin = tmp_yyin;
934 	   	yy_switch_to_buffer(yy_create_buffer(yyin,YY_BUF_SIZE));
935 		file_name = tmp_file_name;
936 		push(file_name, 3, 1);
937    	        line_count = 1;
938 	    }
939           else
940 	  {
941 	       --istackp;
942 	       free(tmp_file_name);
943 	       printf("\"%s\", line %d: Could not open \"%s\"\n",
944 			file_name,
945 			line_count,
946 			file_nam);
947 	       input();
948 	  }
949 	 }
950 }
951 
952 void pop()
953 {
954     if ( gstackp == 0 )
955     {
956        	fprintf(stderr, "%s: Stack underflow\n", PROGNAME);
957 	exit(4);
958     }
959     --gstackp;
960 
961     free(gstack[gstackp].s_name);
962     free(gstack[gstackp].s_file);
963 }
964 
965 char *bg_command(name)
966 char *name;
967 {
968 
969     switch (CG_TYPE) {
970 
971     case 2:
972 	(void) strcpy( returnval, "\\begin{" );
973 	(void) strcat( returnval, (char *) name);
974 	(void) strcat( returnval, "}" );
975 	break;
976 
977     case 3:
978 	(void) strcpy( returnval, "beginning of file " );
979 	(void) strcat( returnval, (char *) name);
980 	break;
981 
982     case 4:
983 	(void) strcpy( returnval, "math begin " );
984 	(void) strcat( returnval, (char *) name);
985 	break;
986 
987     case 5:
988 	(void) strcpy( returnval, "display math begin " );
989 	(void) strcat( returnval, (char *) name);
990 	break;
991 
992     default:
993     	(void) strcpy( returnval, name );
994 
995     }
996 
997     return ((char *)returnval);
998 }
999 
1000 char *eg_command(name,type)
1001 int type;
1002 char *name;
1003 {
1004 
1005     switch (type) {
1006 
1007     case 2:
1008 	(void) strcpy( returnval, "\\end{" );
1009 	(void) strcat( returnval, (char *) name);
1010 	(void) strcat( returnval, "}" );
1011 	break;
1012 
1013     case 3:
1014 	(void) strcpy( returnval, "end of file " );
1015 	(void) strcat( returnval, (char *) name);
1016 	break;
1017 
1018     case 4:
1019 	(void) strcpy( returnval, "math end " );
1020 	(void) strcat( returnval, (char *) name);
1021 	break;
1022 
1023     case 5:
1024 	(void) strcpy( returnval, "display math end " );
1025 	(void) strcat( returnval, (char *) name);
1026 	break;
1027 
1028     default:
1029     	(void) strcpy( returnval, name );
1030 	break;
1031     }
1032 
1033     return ((char *)returnval);
1034 }
1035 
1036 
1037 void g_checkend(n)
1038 int n;
1039 {
1040     if ( check_top_level_end(yytext,n) == 1 )
1041        if (  CG_TYPE != n  )
1042 	 print_bad_match(yytext,n);
1043        else
1044 	pop();
1045 }
1046 
1047 void e_checkend(n, name)
1048 int n;
1049 char *name;
1050 {
1051    if ( check_top_level_end(name,n) == 1 )
1052     {
1053      if (  CG_TYPE != n  ||  strcmp( CG_NAME, name ) != 0 )
1054     	print_bad_match(name,n);
1055 
1056      pop();
1057 
1058     }
1059 }
1060 
1061 void f_checkend(name)
1062 char *name;
1063 {
1064     if ( check_top_level_end(name,3) == 1 )
1065      {
1066        if (  CG_TYPE != 3  ||  strcmp( CG_NAME, name ) != 0 )
1067 
1068     	while( CG_TYPE != 3  )
1069 	{
1070 	  print_bad_match(name,3);
1071           pop();
1072         }
1073 
1074          pop();
1075      }
1076 }
1077 
1078 void print_bad_match(end_command,type)
1079 char *end_command;
1080 int type;
1081 {
1082 	  printf("\"%s\", line %d: <- unmatched \"%s\"\n",
1083 	         file_name,
1084 		 line_count,
1085 		 eg_command( end_command , type) ) ;
1086 
1087 	  printf("\"%s\", line %d: -> unmatched \"%s\"\n",
1088 	         CG_FILE,
1089 		 CG_LINE,
1090 		 bg_command( CG_NAME ) ) ;
1091 	  warn_count += 2;
1092 }
1093 
1094 int check_top_level_end(end_command,type)
1095 char *end_command;
1096 int type;
1097 {
1098     if ( gstackp == 0 )
1099 	{
1100 	 printf("\"%s\", line %d: \"%s\" found at top level\n",
1101 	       file_name,
1102 	       line_count,
1103 	       eg_command( end_command, type )) ;
1104 	 ++warn_count;
1105          return(0);
1106 	}
1107     else
1108     	return(1);
1109 }
1110 
1111 void linecount()
1112 {
1113   int i;
1114   for (i = 0; i < yyleng; i++)
1115     if(yytext[i] == '\n')
1116       line_count++;
1117 }
1118 
1119