1 /*
2  *  ChkTeX, error searching & report routines.
3  *  Copyright (C) 1995-96 Jens T. Berger Thielemann
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  *
19  *  Contact the author at:
20  *              Jens Berger
21  *              Spektrumvn. 4
22  *              N-0666 Oslo
23  *              Norway
24  *              E-mail: <jensthi@ifi.uio.no>
25  *
26  *
27  */
28 
29 
30 #include "ChkTeX.h"
31 #include "FindErrs.h"
32 #include "OpSys.h"
33 #include "Utility.h"
34 #include "Resource.h"
35 
36 #if HAVE_PCRE || HAVE_POSIX_ERE
37 
38 #if HAVE_PCRE
39 #include <pcreposix.h>
40 #else
41 #include <regex.h>
42 #endif
43 
44 #define REGEX_FLAGS REG_EXTENDED
45 #define NUM_MATCHES 10
46 #define ERROR_STRING_SIZE 100
47 
48 regex_t* RegexArray = NULL;
49 regex_t* SilentRegex = NULL;
50 int NumRegexes = 0;
51 
52 #endif
53 
54 /***************************** ERROR MESSAGES ***************************/
55 
56 #undef MSG
57 #define MSG(num, type, inuse, ctxt, text) {num, type, inuse, ctxt, text},
58 
59 struct ErrMsg LaTeXMsgs[emMaxFault + 1] = {
60     ERRMSGS {emMaxFault, etErr, iuOK, 0, INTERNFAULT}
61 };
62 
63 #define istex(c)        (isalpha((unsigned char)c) || (AtLetter && (c == '@')))
64 #define CTYPE(func) \
65 static int my_##func(int c) \
66 { \
67    return(func(c)); \
68 }
69 
70 #define SUPPRESSED_ON_LINE(c)  (LineSuppressions & ((uint64_t)1<<c))
71 
72 #define INUSE(c) \
73     ((LaTeXMsgs[(enum ErrNum) c].InUse == iuOK) && !SUPPRESSED_ON_LINE(c))
74 
75 #define PSERR2(pos,len,err,a,b) \
76     PrintError(CurStkName(&InputStack), RealBuf, pos, len, Line, err, a, b)
77 
78 #define PSERRA(pos,len,err,a) \
79     PrintError(CurStkName(&InputStack), RealBuf, pos, len, Line, err, a)
80 
81 #define HEREA(len, err, a)     PSERRA(BufPtr - Buf - 1, len, err, a)
82 #define PSERR(pos,len,err)     PSERRA(pos,len,err,"")
83 
84 #define HERE(len, err)         HEREA(len, err, "")
85 
86 #define SKIP_BACK(ptr, c, check) \
87     while((c = *ptr--)) \
88     { \
89         if (!(check))  break; \
90     } \
91     ptr++;
92 
93 #define SKIP_AHEAD(ptr, c, check) \
94     while((c = *ptr++)) \
95     { \
96         if (!(check)) \
97             break; \
98     } \
99     ptr--;
100 
101 
102 /*  -=><=- -=><=- -=><=- -=><=- -=><=- -=><=- -=><=- -=><=- -=><=-  */
103 
104 /*
105  * A list of characters LaTeX considers as an end-of-sentence characters, which
106  * should be detected when whether sentence spacing is correct.
107  *
108  */
109 static const char LTX_EosPunc[] = { '.', ':', '?', '!', 0 };
110 
111 /*
112  * General punctuation characters used on your system.
113  */
114 static const char LTX_GenPunc[] = { ',', ';', 0 };
115 
116 /*
117  * A list of characters LaTeX considers as an small punctuation characters,
118  * which should not be preceded by a \/.
119  */
120 static const char LTX_SmallPunc[] = { '.', ',', 0 };
121 
122 /*
123  * String used to delimit a line suppression.  This string must be
124  * followed immediately by the number of the warning to be suppressed.
125  * If more than one warning is to be suppressed, then multiple copies
126  * of LineSuppDelim+number must be used.
127  */
128 const char LineSuppDelim[] = "chktex ";
129 
130 /*
131  * String used to delimit a file suppression.  This string must be
132  * followed immediately by the number of the warning to be suppressed.
133  * If more than one warning is to be suppressed, then multiple copies
134  * of FileSuppDelim+number must be used.
135  */
136 const char FileSuppDelim[] = "chktex-file ";
137 
138 /*
139  * A bit field used to hold the suppressions for the current line.
140  */
141 static uint64_t LineSuppressions;
142 /*
143  * A bit field used to hold the suppressions of numbered user warnings
144  * for the current line.
145  */
146 static uint64_t UserLineSuppressions;
147 
148 static unsigned long Line;
149 
150 static const char *RealBuf;
151 static char *LineCpy = NULL;
152 static char *BufPtr;
153 
154 static int ItFlag = efNone;
155 static int MathFlag = efNone;
156 
157 NEWBUF(Buf, BUFSIZ);
158 NEWBUF(CmdBuffer, BUFSIZ);
159 NEWBUF(ArgBuffer, BUFSIZ);
160 
161 static enum ErrNum PerformCommand(const char *Cmd, char *Arg);
162 
163 #ifdef isdigit
164 CTYPE(isdigit)
165 #else
166 #  define my_isdigit isdigit
167 #endif
168 
169 #ifdef isalpha
CTYPE(isalpha)170 CTYPE(isalpha)
171 #else
172 #  define my_isalpha isalpha
173 #endif
174 
175 /*
176  * Reads in a TeX token from Src and puts it in Dest.
177  *
178  */
179 
180 
181 static char *GetLTXToken(char *Src, char *Dest)
182 {
183     int Char;
184 
185     if (Src && *Src)
186     {
187         if (*Src == '\\')
188         {
189             *Dest++ = *Src++;
190             Char = *Dest++ = *Src++;
191 
192             if (istex(Char))
193             {
194                 while (istex(Char))
195                     Char = *Dest++ = *Src++;
196 
197                 Src--;
198                 Dest--;
199             }
200 
201         }
202         else
203             *Dest++ = *Src++;
204         *Dest = 0;
205     }
206     else
207         Src = NULL;
208 
209     return (Src);
210 }
211 
212 
213 /*
214  * Scans the `SrcBuf' for a LaTeX arg, and puts that arg into `Dest'.
215  * `Until' specifies what we'll copy. Assume the text is
216  * "{foo}bar! qux} baz".
217  *  GET_TOKEN       => "{foo}"
218  *  GET_STRIP_TOKEN => "foo"
219  *  '!'             => "{foo}bar!" (i.e. till the first "!")
220  * Returns NULL if we can't find the argument, ptr to the first character
221  * after the argument in other cases.
222  *
223  * If one of the tokens found is in the wl wordlist, and we're in the
224  * outer most paren, and Until isn't a single character, we'll stop.
225  * You may pass NULL as wl.
226  *
227  * We assume that you've previously skipped over leading spaces.
228  *
229  */
230 
231 #define GET_TOKEN       256
232 #define GET_STRIP_TOKEN 257
233 
GetLTXArg(char * SrcBuf,char * OrigDest,const int Until,struct WordList * wl)234 static char *GetLTXArg(char *SrcBuf, char *OrigDest, const int Until,
235 	struct WordList *wl)
236 {
237     char *Retval;
238     char *TmpPtr;
239     char *Dest = OrigDest;
240     unsigned long DeliCnt = 0;
241 
242     *Dest = 0;
243     TmpPtr = SrcBuf;
244 
245     switch (Until)
246     {
247     case GET_STRIP_TOKEN:
248     case GET_TOKEN:
249         while ((Retval = GetLTXToken(TmpPtr, Dest)))
250         {
251             switch (*Dest)
252             {
253             case '{':
254                 DeliCnt++;
255                 break;
256             case '}':
257                 DeliCnt--;
258             }
259             Dest += Retval - TmpPtr;
260             TmpPtr = Retval;
261 
262             if (!DeliCnt || ((DeliCnt == 1) && wl && HasWord(Dest, wl)))
263                 break;
264         }
265 
266         if (Retval && (*OrigDest == '{') && (Until == GET_STRIP_TOKEN))
267         {
268             int len = strlen(OrigDest+1);
269             memmove(OrigDest, OrigDest + 1, len  + 1);
270             /* Strip the last '}' off */
271             OrigDest[len-1] = 0;
272         }
273         break;
274     default:
275         DeliCnt = TRUE;
276         while ((Retval = GetLTXArg(TmpPtr, Dest, GET_TOKEN, NULL)))
277         {
278             if (*Dest == Until)
279                 DeliCnt = FALSE;
280 
281             Dest += Retval - TmpPtr;
282             TmpPtr = Retval;
283 
284             if (!DeliCnt)
285                 break;
286         }
287         break;
288     }
289     *Dest = 0;
290 
291     return (Retval);
292 }
293 
294 
MakeCpy(void)295 static char *MakeCpy(void)
296 {
297     if (!LineCpy)
298         LineCpy = strdup(RealBuf);
299 
300     if (!LineCpy)
301         PrintPrgErr(pmStrDupErr);
302 
303     return (LineCpy);
304 }
305 
PreProcess(void)306 static char *PreProcess(void)
307 {
308     char *TmpPtr;
309 
310     /* Reset any line suppressions  */
311 
312     LineSuppressions = FileSuppressions;
313     UserLineSuppressions = UserFileSuppressions;
314 
315     /* Kill comments. */
316     strcpy(Buf, RealBuf);
317 
318     TmpPtr = Buf;
319 
320     while ((TmpPtr = strchr(TmpPtr, '%')))
321     {
322         char *EscapePtr = TmpPtr;
323         int NumBackSlashes = 0;
324         while (EscapePtr != Buf && EscapePtr[-1] == '\\')
325         {
326             ++NumBackSlashes;
327             --EscapePtr;
328         }
329 
330         /* If there is an even number of backslashes, then it's a comment. */
331         if ((NumBackSlashes % 2) == 0)
332         {
333             PSERR(TmpPtr - Buf, 1, emComment);
334             *TmpPtr = 0;
335             /* Check for line suppressions */
336             if (!NoLineSupp)
337             {
338                 int error;
339                 const int MaxSuppressionBits = 63;
340 
341                 /* Convert to lowercase to compare with LineSuppDelim */
342                 EscapePtr = ++TmpPtr; /* move past NUL terminator */
343                 while ( *EscapePtr )
344                 {
345                     *EscapePtr = tolower((unsigned char)*EscapePtr);
346                     ++EscapePtr;
347                 }
348 
349                 EscapePtr = TmpPtr; /* Save it for later */
350                 while ((TmpPtr = strstr(TmpPtr, FileSuppDelim))) {
351                     TmpPtr += STRLEN(FileSuppDelim);
352                     error = atoi(TmpPtr);
353 
354                     if (abs(error) > MaxSuppressionBits)
355                     {
356                         PrintPrgErr(pmSuppTooHigh, error, MaxSuppressionBits);
357                     }
358                     if (error > 0)
359                     {
360                         FileSuppressions |= ((uint64_t)1 << error);
361                         LineSuppressions |= ((uint64_t)1 << error);
362                     }
363                     else
364                     {
365                         UserFileSuppressions |= ((uint64_t)1 << (-error));
366                         UserLineSuppressions |= ((uint64_t)1 << (-error));
367                     }
368                 }
369                 TmpPtr = EscapePtr;
370 
371                 while ((TmpPtr = strstr(TmpPtr, LineSuppDelim))) {
372 
373                     TmpPtr += STRLEN(LineSuppDelim);
374                     error = atoi(TmpPtr);
375 
376                     if (abs(error) > MaxSuppressionBits)
377                     {
378                         PrintPrgErr(pmSuppTooHigh, error, MaxSuppressionBits);
379                     }
380 
381                     if (error > 0)
382                     {
383                         LineSuppressions |= ((uint64_t)1 << error);
384                     }
385                     else
386                     {
387                         UserLineSuppressions |= ((uint64_t)1 << (-error));
388                     }
389                 }
390             }
391             break;
392         }
393         TmpPtr++;
394     }
395     return (Buf);
396 }
397 
398 /*
399  * Interpret environments
400  */
401 
PerformEnv(char * Env,int Begin)402 static void PerformEnv(char *Env, int Begin)
403 {
404     static char VBStr[BUFSIZ] = "";
405 
406     if (HasWord(Env, &MathEnvir))
407     {
408         MathMode += Begin ? 1 : -1;
409         MathMode = max(MathMode, 0);
410     }
411 
412     if (Begin && HasWord(Env, &VerbEnvir))
413     {
414         VerbMode = TRUE;
415         strcpy(VBStr, "\\end{");
416         strcat(VBStr, Env);
417         strcat(VBStr, "}");
418         VerbStr = VBStr;
419     }
420 }
421 
SkipVerb(void)422 static char *SkipVerb(void)
423 {
424     char *TmpPtr = BufPtr;
425     int TmpC;
426 
427     if (VerbMode && BufPtr)
428     {
429         if (!(TmpPtr = strstr(BufPtr, VerbStr)))
430             BufPtr = &BufPtr[strlen(BufPtr)];
431         else
432         {
433             VerbMode = FALSE;
434             BufPtr = &TmpPtr[strlen(VerbStr)];
435             SKIP_AHEAD(BufPtr, TmpC, LATEX_SPACE(TmpC));
436             if (*BufPtr)
437                 PSERR(BufPtr - Buf, strlen(BufPtr) - 2, emIgnoreText);
438         }
439     }
440     return (TmpPtr);
441 }
442 
443 #define CHECKDOTS(wordlist, dtval) \
444 for(i = 0; (i < wordlist.Stack.Used) && !(Back && Front);  i++) \
445  { if(!strafter(PstPtr, wordlist.Stack.Data[i])) \
446          Back = dtval; \
447    if(!strinfront(PrePtr, wordlist.Stack.Data[i])) \
448          Front = dtval; }
449 
450 
451 
452 /*
453  * Checks that the dots are correct
454  */
455 
CheckDots(char * PrePtr,char * PstPtr)456 static enum DotLevel CheckDots(char *PrePtr, char *PstPtr)
457 {
458     unsigned long i;
459     int TmpC;
460     enum DotLevel Front = dtUnknown, Back = dtUnknown;
461 
462     if (MathMode)
463     {
464         PrePtr--;
465 #define SKIP_EMPTIES(macro, ptr) macro(ptr, TmpC, \
466 (LATEX_SPACE(TmpC) || (TmpC == '{') || (TmpC == '}')))
467 
468         SKIP_EMPTIES(SKIP_BACK, PrePtr);
469         SKIP_EMPTIES(SKIP_AHEAD, PstPtr);
470 
471         CHECKDOTS(CenterDots, dtCDots);
472 
473         if (!(Front && Back))
474         {
475             CHECKDOTS(LowDots, dtLDots);
476         }
477         return (Front & Back);
478     }
479     else
480         return (dtLDots);
481 
482 }
483 
Dot2Str(enum DotLevel dl)484 static const char *Dot2Str(enum DotLevel dl)
485 {
486     const char *Retval = INTERNFAULT;
487     switch (dl)
488     {
489     case dtUnknown:
490         Retval = "\\cdots or \\ldots";
491         break;
492     case dtDots:
493         Retval = "\\dots";
494         break;
495     case dtCDots:
496         Retval = "\\cdots";
497         break;
498     case dtLDots:
499         Retval = "\\ldots";
500         break;
501     }
502     return Retval;
503 }
504 
505 /*
506  * Wipes a command, according to the definition in WIPEARG
507  */
508 
WipeArgument(const char * Cmd,char * CmdPtr)509 static void WipeArgument(const char *Cmd, char *CmdPtr)
510 {
511     unsigned long CmdLen = strlen(Cmd);
512     const char *Format;
513     char *TmpPtr;
514     int c, TmpC;
515 
516     if (Cmd && *Cmd)
517     {
518         TmpPtr = &CmdPtr[CmdLen];
519         Format = &Cmd[CmdLen + 1];
520 
521         while (TmpPtr && *TmpPtr && *Format)
522         {
523             switch (c = *Format++)
524             {
525             case '*':
526                 SKIP_AHEAD(TmpPtr, TmpC, LATEX_SPACE(TmpC));
527                 if (*TmpPtr == '*')
528                     TmpPtr++;
529                 break;
530             case '[':
531                 SKIP_AHEAD(TmpPtr, TmpC, LATEX_SPACE(TmpC));
532                 if (*TmpPtr == '[')
533                     TmpPtr = GetLTXArg(TmpPtr, ArgBuffer, ']', NULL);
534                 break;
535             case '(':
536                 SKIP_AHEAD(TmpPtr, TmpC, LATEX_SPACE(TmpC));
537                 if (*TmpPtr == '(')
538                     TmpPtr = GetLTXArg(TmpPtr, ArgBuffer, ')', NULL);
539                 break;
540             case '{':
541                 SKIP_AHEAD(TmpPtr, TmpC, LATEX_SPACE(TmpC));
542                 TmpPtr = GetLTXArg(TmpPtr, ArgBuffer, GET_TOKEN, NULL);
543             case '}':
544             case ']':
545             case ')':
546                 break;
547             default:
548                 PrintPrgErr(pmWrongWipeTemp, &Cmd[strlen(Cmd) + 1]);
549                 break;
550             }
551         }
552 
553         if (TmpPtr)
554             strwrite(CmdPtr+CmdLen, VerbClear, TmpPtr - CmdPtr - CmdLen);
555         else
556             strxrep(CmdPtr+CmdLen, "()[]{}", *VerbClear);
557     }
558 }
559 
560 /*
561  * Checks italic.
562  *
563  */
564 
CheckItal(const char * Cmd)565 static void CheckItal(const char *Cmd)
566 {
567     int TmpC;
568     char *TmpPtr;
569     if (HasWord(Cmd, &NonItalic))
570         ItState = itOff;
571     else if (HasWord(Cmd, &Italic))
572         ItState = itOn;
573     else if (HasWord(Cmd, &ItalCmd))
574     {
575         TmpPtr = BufPtr;
576         SKIP_AHEAD(TmpPtr, TmpC, LATEX_SPACE(TmpC));
577         if (*TmpPtr == '{')
578         {
579             ItFlag = ItState ? efItal : efNoItal;
580             ItState = itOn;
581         }
582     }
583 }
584 
585 /*
586  * Interpret isolated commands.
587  *
588  */
589 
PerformBigCmd(char * CmdPtr)590 static void PerformBigCmd(char *CmdPtr)
591 {
592     char *TmpPtr;
593     const char *ArgEndPtr;
594     unsigned long CmdLen = strlen(CmdBuffer);
595     int TmpC;
596     enum ErrNum ErrNum;
597     struct ErrInfo *ei;
598 
599     enum DotLevel dotlev, realdl = dtUnknown;
600 
601     TmpPtr = BufPtr;
602     SKIP_AHEAD(TmpPtr, TmpC, LATEX_SPACE(TmpC));
603 
604     ArgEndPtr = GetLTXArg(TmpPtr, ArgBuffer, GET_STRIP_TOKEN, NULL);
605 
606     /* Kill `\verb' commands */
607 
608     if (WipeVerb)
609     {
610         if (!strcmp(CmdBuffer, "\\verb"))
611         {
612             if (*BufPtr && (*BufPtr != '*' || BufPtr[1]))
613             {
614                 if (*BufPtr == '*')
615                     TmpPtr = strchr(&BufPtr[2], BufPtr[1]);
616                 else
617                     TmpPtr = strchr(&BufPtr[1], *BufPtr);
618                 if (TmpPtr)
619                     strwrite(CmdPtr, VerbClear, (TmpPtr - CmdPtr) + 1);
620                 else
621                     PSERR(CmdPtr - Buf, 5, emNoArgFound);
622             }
623         }
624     }
625 
626     if (HasWord(CmdBuffer, &IJAccent))
627     {
628         if (ArgEndPtr)
629         {
630             TmpPtr = ArgBuffer;
631             SKIP_AHEAD(TmpPtr, TmpC, TmpC == '{');      /* } */
632 
633             if ((*TmpPtr == 'i') || (*TmpPtr == 'j'))
634                 PrintError(CurStkName(&InputStack), RealBuf,
635                            CmdPtr - Buf,
636                            (long) strlen(CmdBuffer), Line,
637                            emAccent, CmdBuffer, *TmpPtr,
638                            MathMode ? "math" : "");
639         }
640         else
641             PSERR(CmdPtr - Buf, CmdLen, emNoArgFound);
642     }
643 
644     if (HasWord(CmdBuffer, &NotPreSpaced) && isspace((unsigned char)CmdPtr[-1]))
645         PSERRA(CmdPtr - Buf - 1, 1, emRemPSSpace, CmdBuffer);
646 
647     if ((TmpPtr = HasWord(CmdBuffer, &NoCharNext)))
648     {
649         char *BPtr = BufPtr;
650 
651         TmpPtr += strlen(TmpPtr) + 1;
652         SKIP_AHEAD(BPtr, TmpC, LATEX_SPACE(TmpC));
653 
654         if (strchr(TmpPtr, *BPtr))
655         {
656             PSERR2(CmdPtr - Buf, CmdLen, emNoCharMean, CmdBuffer, *BPtr);
657         }
658     }
659 
660     /* LaTeX environment tracking */
661     if (!strcmp(CmdBuffer, "\\begin") || !strcmp(CmdBuffer, "\\end"))
662     {
663         if (ArgEndPtr)
664         {
665             if (!strcmp(ArgBuffer, "document"))
666                 InHeader = FALSE;
667 
668             if (CmdBuffer[1] == 'b')
669             {
670                 if (!(PushErr(ArgBuffer, Line, CmdPtr - Buf,
671                               CmdLen, MakeCpy(), &EnvStack)))
672                     PrintPrgErr(pmNoStackMem);
673             }
674             else
675             {
676                 if ((ei = PopErr(&EnvStack)))
677                 {
678                     if (strcmp(ei->Data, ArgBuffer))
679                         PrintError(CurStkName(&InputStack), RealBuf,
680                                    CmdPtr - Buf,
681                                    (long) strlen(CmdBuffer),
682                                    Line, emExpectC, ei->Data, ArgBuffer);
683 
684                     FreeErrInfo(ei);
685                 }
686                 else
687                     PrintError(CurStkName(&InputStack), RealBuf,
688                                CmdPtr - Buf,
689                                (long) strlen(CmdBuffer),
690                                Line, emSoloC, ArgBuffer);
691             }
692 
693             PerformEnv(ArgBuffer, (int) CmdBuffer[1] == 'b');
694         }
695         else
696             PSERR(CmdPtr - Buf, CmdLen, emNoArgFound);
697     }
698 
699     /* ConTeXt \start \stop tracking */
700     if (!strncmp(CmdBuffer, "\\start", 6) || !strncmp(CmdBuffer, "\\stop", 5))
701     {
702         if (CmdBuffer[3] == 'a') /* start */
703         {
704             TmpPtr = CmdBuffer + 6;
705             if (!(PushErr(TmpPtr, Line, CmdPtr - Buf + 6,
706                           CmdLen - 6, MakeCpy(), &EnvStack)))
707                 PrintPrgErr(pmNoStackMem);
708         }
709         else
710         {
711             TmpPtr = CmdBuffer + 5;
712             if ((ei = PopErr(&EnvStack)))
713             {
714                 if (strcmp(ei->Data, TmpPtr))
715                     PrintError(CurStkName(&InputStack), RealBuf,
716                                CmdPtr - Buf + 5,
717                                (long) strlen(TmpPtr),
718                                Line, emExpectC, ei->Data, TmpPtr);
719 
720                 FreeErrInfo(ei);
721             }
722             else
723             {
724                 PrintError(CurStkName(&InputStack), RealBuf,
725                            CmdPtr - Buf,
726                            (long) strlen(CmdBuffer),
727                            Line, emSoloC, TmpPtr);
728             }
729         }
730         /* TODO: Do I need to call PerformEnv? */
731         /* It handles math and verbatim environments */
732     }
733 
734     CheckItal(CmdBuffer);
735 
736     if ((ErrNum = PerformCommand(CmdBuffer, BufPtr)))
737         PSERR(CmdPtr - Buf, CmdLen, ErrNum);
738 
739     if (!strcmp(CmdBuffer, "\\cdots"))
740         realdl = dtCDots;
741 
742     if (!strcmp(CmdBuffer, "\\ldots"))
743         realdl = dtLDots;
744 
745     if (!strcmp(CmdBuffer, "\\dots"))
746         realdl = dtLDots;
747 
748     if (realdl != dtUnknown)
749     {
750         dotlev = CheckDots(CmdPtr, BufPtr);
751         if (dotlev && (dotlev != realdl))
752         {
753             const char *cTmpPtr = Dot2Str(dotlev);
754             PSERRA(CmdPtr - Buf, CmdLen, emEllipsis, cTmpPtr);
755         }
756     }
757 
758     if ((TmpPtr = HasWord(CmdBuffer, &WipeArg)))
759         WipeArgument(TmpPtr, CmdPtr);
760 }
761 
762 /*
763  * Check user abbreviations. Pass a pointer to the `.';
764  * also ensure that it's followed by spaces, etc.
765  *
766  * Note: We assume that all abbrevs have been transferred from
767  * AbbrevCase into Abbrev.
768  */
769 
CheckAbbrevs(const char * Buffer)770 static void CheckAbbrevs(const char *Buffer)
771 {
772     long i;
773     char *TmpPtr;
774     const char *AbbPtr;
775 
776     if (INUSE(emInterWord))
777     {
778         TmpPtr = TmpBuffer + Abbrev.MaxLen + 2;
779         *TmpPtr = 0;
780         AbbPtr = Buffer;
781 
782         for (i = Abbrev.MaxLen; i >= 0; i--)
783         {
784             *--TmpPtr = *AbbPtr--;
785             if (!isalpha((unsigned char)*AbbPtr) && HasWord(TmpPtr, &Abbrev))
786                 PSERR(Buffer - Buf + 1, 1, emInterWord);
787             if (!*AbbPtr)
788                 break;
789         }
790     }
791 }
792 
793 
794 /*
795  * Check misc. things which can't be included in the main loop.
796  *
797  */
798 
CheckRest(void)799 static void CheckRest(void)
800 {
801     unsigned long Count;
802     long CmdLen;
803     char *UsrPtr;
804 
805     /* Search for user-specified warnings */
806 
807 #if ! (HAVE_PCRE || HAVE_POSIX_ERE)
808 
809     if (INUSE(emUserWarnRegex) && UserWarnRegex.Stack.Used > 0)
810     {
811         PrintPrgErr(pmNoRegExp);
812         ClearWord( &UserWarnRegex );
813     }
814     else if (INUSE(emUserWarn))
815     {
816         strcpy(TmpBuffer, Buf);
817     }
818 
819 #else
820 
821     if (INUSE(emUserWarnRegex) && UserWarnRegex.Stack.Used > 0)
822     {
823         static char error[ERROR_STRING_SIZE];
824         static regmatch_t MatchVector[NUM_MATCHES];
825         int rc;
826         int len = strlen(TmpBuffer);
827         strcpy(TmpBuffer, Buf);
828 
829         /* Compile all regular expressions if not already compiled. */
830         if ( !RegexArray && UserWarnRegex.Stack.Used > 0 )
831         {
832             RegexArray = (regex_t*)malloc( sizeof(regex_t) * UserWarnRegex.Stack.Used );
833             if (!RegexArray)
834             {
835                 /* Allocation failed. */
836                 PrintPrgErr(pmNoRegexMem);
837                 ClearWord(&UserWarnRegex);
838                 NumRegexes = 0;
839             }
840             else
841             {
842                 NumRegexes = 0;
843                 FORWL(Count, UserWarnRegex)
844                 {
845                     char *pattern = UserWarnRegex.Stack.Data[Count];
846                     char *CommentEnd = NULL;
847 
848                     /* See if it's got a special name that it goes by.
849                        Only use the comment if it's at the very beginning. */
850                     if ( strncmp(pattern,"(?#",3) == 0 )
851                     {
852                         CommentEnd = strchr(pattern, ')');
853                         /* TODO: check for PCRE/POSIX only regexes */
854                         *CommentEnd = '\0';
855                         /* We're leaking a little here, but this was never freed until exit anyway... */
856                         UserWarnRegex.Stack.Data[NumRegexes] = pattern+3;
857 
858                         /* Compile past the end of the comment so that it works with POSIX too. */
859                         pattern = CommentEnd + 1;
860                     }
861 
862                     /* Ignore PCRE and POSIX specific regexes.
863                      * This is mostly to make testing easier. */
864                     if ( strncmp(pattern,"PCRE:",5) == 0 )
865                     {
866                         #if HAVE_PCRE
867                         pattern += 5;
868                         #else
869                         continue;
870                         #endif
871                     }
872                     if ( strncmp(pattern,"POSIX:",6) == 0 )
873                     {
874                         #if HAVE_POSIX_ERE
875                         pattern += 6;
876                         #else
877                         continue;
878                         #endif
879                     }
880 
881                     rc = regcomp((regex_t*)(&RegexArray[NumRegexes]),
882                                  pattern, REGEX_FLAGS);
883 
884                     /* Compilation failed: print the error message */
885                     if (rc != 0)
886                     {
887                         /* TODO: decide whether a non-compiling regex should completely stop, or just be ignored */
888                         regerror(rc,(regex_t*)(&RegexArray[NumRegexes]),
889                                  error, ERROR_STRING_SIZE);
890                         PrintPrgErr(pmRegexCompileFailed, pattern, error);
891                     }
892                     else
893                     {
894                         if ( !CommentEnd )
895                         {
896                             ((char*)UserWarnRegex.Stack.Data[NumRegexes])[0] = '\0';
897                         }
898                         ++NumRegexes;
899                     }
900                 }
901             }
902         }
903 
904         for (Count = 0; Count < NumRegexes; ++Count)
905         {
906             int offset = 0;
907             char *ErrMessage = UserWarnRegex.Stack.Data[Count];
908             const int NamedWarning = strlen(ErrMessage) > 0;
909 
910             while (offset < len)
911             {
912                 /* Check if this warning should be suppressed. */
913                 if (UserLineSuppressions && NamedWarning)
914                 {
915                     /* The warning can be named with positive or negative numbers. */
916                     int UserWarningNumber = abs(atoi(ErrMessage));
917                     if (UserLineSuppressions & ((uint64_t)1 << UserWarningNumber))
918                     {
919                         break;
920                     }
921                 }
922 
923                 rc = regexec( (regex_t*)(&RegexArray[Count]), TmpBuffer+offset,
924                               NUM_MATCHES, MatchVector, 0);
925                 /* Matching failed: handle error cases */
926                 if (rc != 0)
927                 {
928                     switch(rc)
929                     {
930                         case REG_NOMATCH:
931                             /* no match, no problem */
932                             break;
933                         default:
934                             regerror(rc, (regex_t*)(&RegexArray[Count]),
935                                      error, ERROR_STRING_SIZE);
936                             PrintPrgErr(pmRegexMatchingError, error);
937                             break;
938                     }
939 
940                     offset = len; /* break out of loop */
941                 }
942                 else
943                 {
944 #define MATCH (MatchVector[0])
945                     if ( NamedWarning )
946                     {
947                         /* User specified error message */
948                         PSERR2(offset + MATCH.rm_so, MATCH.rm_eo - MATCH.rm_so,
949                                emUserWarnRegex,
950                                strlen(ErrMessage), ErrMessage);
951                     }
952                     else
953                     {
954                         /* Default -- show the match */
955                         PSERR2(offset + MATCH.rm_so, MATCH.rm_eo - MATCH.rm_so,
956                                emUserWarnRegex,
957                                /* The format specifier expects an int */
958                                (int)(MATCH.rm_eo - MATCH.rm_so),
959                                TmpBuffer + offset + MATCH.rm_so);
960                     }
961                     offset += MATCH.rm_eo;
962 #undef MATCH
963                 }
964             }
965         }
966     }
967     else if (INUSE(emUserWarn))
968     {
969         strcpy(TmpBuffer, Buf);
970     }
971 
972 #endif
973 
974 
975     if (INUSE(emUserWarn))
976     {
977         FORWL(Count, UserWarn)
978         {
979             for (UsrPtr = TmpBuffer;
980                  (UsrPtr = strstr(UsrPtr, UserWarn.Stack.Data[Count]));
981                  UsrPtr++)
982             {
983                 CmdLen = strlen(UserWarn.Stack.Data[Count]);
984                 PSERRA(UsrPtr - TmpBuffer, CmdLen, emUserWarn, UserWarn.Stack.Data[Count]);
985             }
986         }
987 
988         strlwr(TmpBuffer);
989 
990         FORWL(Count, UserWarnCase)
991         {
992             for (UsrPtr = TmpBuffer;
993                  (UsrPtr = strstr(UsrPtr, UserWarnCase.Stack.Data[Count]));
994                  UsrPtr++)
995             {
996                 CmdLen = strlen(UserWarnCase.Stack.Data[Count]);
997                 PSERRA(UsrPtr - TmpBuffer, CmdLen, emUserWarn, UserWarnCase.Stack.Data[Count]);
998             }
999         }
1000     }
1001 }
1002 
1003 
1004 /*
1005  * Checks that the dash-len is correct.
1006  */
1007 
CheckDash(void)1008 static void CheckDash(void)
1009 {
1010     char *TmpPtr;
1011     int TmpC;
1012     long TmpCount, Len;
1013     struct WordList *wl = NULL;
1014     unsigned long i;
1015     int Errored;
1016     char *PrePtr = &BufPtr[-2];
1017 
1018     TmpPtr = BufPtr;
1019     SKIP_AHEAD(TmpPtr, TmpC, TmpC == '-');
1020     TmpCount = TmpPtr - BufPtr + 1;
1021 
1022     if (MathMode)
1023     {
1024         if (TmpCount > 1)
1025             HERE(TmpCount, emWrongDash);
1026     }
1027     else
1028     {
1029         if (LATEX_SPACE(*PrePtr) && LATEX_SPACE(*TmpPtr))
1030             wl = &WordDash;
1031         if (isdigit((unsigned char)*PrePtr) && isdigit((unsigned char)*TmpPtr))
1032             wl = &NumDash;
1033         if (isalpha((unsigned char)*PrePtr) && isalpha((unsigned char)*TmpPtr))
1034             wl = &HyphDash;
1035 
1036         if (wl)
1037         {
1038             Errored = TRUE;
1039             FORWL(i, *wl)
1040             {
1041                 Len = strtol(wl->Stack.Data[i], NULL, 0);
1042                 if (TmpCount == Len)
1043                 {
1044                     Errored = FALSE;
1045                     break;
1046                 }
1047             }
1048 
1049             if (Errored)
1050             {
1051                 struct WordList *el = &DashExcpt;
1052 
1053                 FORWL(i, *el)
1054                 {
1055                     char *exception = el->Stack.Data[i];
1056 
1057                     char *e = exception;
1058                     while ( *e )
1059                     {
1060                         if ( *e == '-' && 0 == strncmp( BufPtr, e, strlen(e) ) )
1061                         {
1062                             char *f = e;
1063                             TmpPtr = BufPtr;
1064                             while ( f > exception && *(--f) == *(--TmpPtr) )
1065                             {
1066                                 /* Nothing */
1067                             }
1068 
1069                             if ( f <= exception && *f == *TmpPtr )
1070                             {
1071                                 Errored = FALSE;
1072                                 break;
1073                             }
1074                         }
1075 
1076                         ++e;
1077                     }
1078 
1079                     if ( !Errored )
1080                         break;
1081                 }
1082             }
1083 
1084             if (Errored)
1085                 HERE(TmpCount, emWrongDash);
1086         }
1087     }
1088 }
1089 
1090 /*
1091  * Pushes and pops nesting characters.
1092  *
1093  */
1094 
HandleBracket(char Char)1095 static void HandleBracket(char Char)
1096 {
1097     unsigned long BrOffset;     /* Offset into BrOrder array */
1098     struct ErrInfo *ei;
1099     char TmpC, Match;
1100     char ABuf[2], BBuf[2];
1101     char *TmpPtr;
1102 
1103     AddBracket(Char);
1104 
1105     if ((BrOffset = BrackIndex(Char)) != ~0UL)
1106     {
1107         if (BrOffset & 1)       /* Closing bracket of some sort */
1108         {
1109             if ((ei = PopErr(&CharStack)))
1110             {
1111                 Match = MatchBracket(*(ei->Data));
1112                 /* Return italics to proper state */
1113                 if (ei->Flags & efNoItal)
1114                 {
1115                     if (ItState == itOn)
1116                     {
1117                         TmpPtr = BufPtr;
1118                         SKIP_AHEAD(TmpPtr, TmpC, TmpC == '}');
1119 
1120                         /* If the next character is a period or comma,
1121                          * or the last character is, then it's not an
1122                          * error. */
1123                         /* Checking 2 characters back seems dangerous,
1124                          * but it's already done in CheckDash. */
1125                         if ( !strchr(LTX_SmallPunc, *TmpPtr) &&
1126                              !strchr(LTX_SmallPunc, *(TmpPtr-2)) )
1127                             HERE(1, emNoItFound);
1128                     }
1129 
1130                     ItState = FALSE;
1131                 }
1132                 else if (ei->Flags & efItal)
1133                     ItState = TRUE;
1134 
1135                 /* Same for math mode */
1136                 if (ei->Flags & efMath)
1137                 {
1138                     MathMode = 1;
1139                 }
1140                 else if (ei->Flags & efNoMath)
1141                 {
1142                     MathMode = 0;
1143                 }
1144 
1145                 FreeErrInfo(ei);
1146             }
1147             else
1148                 Match = 0;
1149 
1150             if (Match != Char)
1151             {
1152                 ABuf[0] = Match;
1153                 BBuf[0] = Char;
1154                 ABuf[1] = BBuf[1] = 0;
1155                 if (Match)
1156                     PrintError(CurStkName(&InputStack), RealBuf,
1157                                BufPtr - Buf - 1, 1, Line, emExpectC,
1158                                ABuf, BBuf);
1159                 else
1160                     HEREA(1, emSoloC, BBuf);
1161             }
1162 
1163         }
1164         else                    /* Opening bracket of some sort  */
1165         {
1166             if ((ei = PushChar(Char, Line, BufPtr - Buf - 1,
1167                                &CharStack, MakeCpy())))
1168             {
1169                 if (Char == '{')
1170                 {
1171                     switch (ItFlag)
1172                     {
1173                     default:
1174                         ei->Flags |= ItFlag;
1175                         ItFlag = efNone;
1176                         break;
1177                     case efNone:
1178                         ei->Flags |= ItState ? efItal : efNoItal;
1179                     }
1180 
1181                     switch (MathFlag)
1182                     {
1183                     default:
1184                         ei->Flags |= MathFlag;
1185                         MathFlag = efNone;
1186                         break;
1187                     case efNone:
1188                         ei->Flags |= MathMode ? efMath : efNoMath;
1189                     }
1190                 }
1191             }
1192 
1193             else
1194                 PrintPrgErr(pmNoStackMem);
1195         }
1196     }
1197 }
1198 
1199 
1200 /*
1201  * Checks to see if CmdBuffer matches any of the words in Silent, or
1202  * any of the regular expressions in SilentCase.
1203  *
1204  */
1205 
CheckSilentRegex(void)1206 int CheckSilentRegex(void)
1207 {
1208 
1209 #if ! (HAVE_PCRE || HAVE_POSIX_ERE)
1210 
1211     return HasWord(CmdBuffer, &Silent) != NULL;
1212 
1213 #else
1214 
1215     static char error[ERROR_STRING_SIZE];
1216     char *pattern;
1217     char *tmp;
1218     int i;
1219     int rc;
1220     int len = 4;                /* Enough for the (?:) */
1221 
1222     /* Initialize regular expression */
1223     if (INUSE(emSpaceTerm) && SilentCase.Stack.Used > 0)
1224     {
1225         /* Find the total length we need */
1226         /* There is 1 for | and the final for null terminator */
1227         FORWL(i, SilentCase)
1228         {
1229             len += strlen( SilentCase.Stack.Data[i] ) + 1;
1230         }
1231 
1232         /* (A|B|...) */
1233         tmp = (pattern = (char*)malloc( sizeof(char) * len ));
1234 
1235         #if HAVE_PCRE
1236         tmp = stpcpy(tmp,"(?:");
1237         #else
1238         tmp = stpcpy(tmp,"(");
1239         #endif
1240 
1241         FORWL(i, SilentCase)
1242         {
1243             tmp = stpcpy(tmp, SilentCase.Stack.Data[i]);
1244             *tmp++ = '|';
1245         }
1246         tmp = stpcpy(tmp - 1, ")");
1247 
1248         SilentRegex = malloc( sizeof(regex_t) );
1249         rc = regcomp(SilentRegex, pattern, REGEX_FLAGS);
1250 
1251         /* Compilation failed: print the error message */
1252         if (rc != 0)
1253         {
1254             regerror(rc, SilentRegex, error, ERROR_STRING_SIZE);
1255             PrintPrgErr(pmRegexCompileFailed, pattern, error);
1256             SilentRegex = NULL;
1257         }
1258         /* Ensure we won't initialize it again */
1259         SilentCase.Stack.Used = 0;
1260         free(pattern);
1261     }
1262 
1263     /* Check against the normal */
1264     if ( HasWord(CmdBuffer, &Silent) )
1265         return 1;
1266     if (!SilentRegex)
1267         return 0;
1268 
1269     /* Check against the regexes */
1270     rc = regexec(SilentRegex, CmdBuffer, 0, NULL, 0);
1271     if (rc == 0)
1272         return 1;
1273 
1274     /* Matching failed: handle error cases */
1275     switch(rc)
1276     {
1277         case REG_NOMATCH:
1278             return 0;
1279             break;
1280         default:
1281             regerror(rc, SilentRegex, error, ERROR_STRING_SIZE);
1282             PrintPrgErr(pmRegexMatchingError, error);
1283             break;
1284     }
1285     return 0;
1286 
1287 #endif
1288 }
1289 
1290 /*
1291  * Searches the `Buf' for possible errors, and prints the errors. `Line'
1292  * is supplied for error printing.
1293  */
1294 
FindErr(const char * _RealBuf,const unsigned long _Line)1295 int FindErr(const char *_RealBuf, const unsigned long _Line)
1296 {
1297     char *CmdPtr;               /* We'll have to copy each command out. */
1298     char *PrePtr;               /* Ptr to char in front of command, NULL if
1299                                  * the cmd appears as the first character  */
1300     char *TmpPtr;               /* Temporary pointer */
1301     char *ErrPtr;               /* Ptr to where an error started */
1302 
1303     char TmpC,                  /* Just a temp var used throughout the proc. */
1304       MatchC, Char;             /* Char. currently processed */
1305     unsigned long CmdLen;       /* Length of misc. things */
1306     int MixingQuotes;
1307 
1308     int (*pstcb) (int c);
1309 
1310     enum DotLevel dotlev;
1311 
1312     if (_RealBuf)
1313     {
1314         RealBuf = _RealBuf;
1315         Line = _Line;
1316 
1317         BufPtr = PreProcess();
1318 
1319         BufPtr = SkipVerb();
1320 
1321         while (BufPtr && *BufPtr)
1322         {
1323             PrePtr = BufPtr - 1;
1324             Char = *BufPtr++;
1325             if (isspace((unsigned char)Char))
1326                 Char = ' ';
1327 
1328             switch (Char)
1329             {
1330             case '~':
1331                 TmpPtr = NULL;
1332                 if (isspace((unsigned char)*PrePtr))
1333                     TmpPtr = PrePtr;
1334                 else if (isspace((unsigned char)*BufPtr))
1335                     TmpPtr = BufPtr;
1336 
1337                 if (TmpPtr)
1338                     PSERR(TmpPtr - Buf, 1, emDblSpace);
1339                 break;
1340 
1341             case 'X':
1342             case 'x':
1343                 TmpPtr = PrePtr;
1344 
1345                 SKIP_BACK(TmpPtr, TmpC,
1346                           (LATEX_SPACE(TmpC) || strchr("{}$", TmpC)));
1347 
1348                 if (isdigit((unsigned char)*TmpPtr))
1349                 {
1350                     TmpPtr = BufPtr;
1351 
1352                     SKIP_AHEAD(TmpPtr, TmpC,
1353                                (LATEX_SPACE(TmpC) || strchr("{}$", TmpC)));
1354 
1355                     if (isdigit((unsigned char)*TmpPtr))
1356                         HERE(1, emUseTimes);
1357                 }
1358                 /* FALLTHRU */
1359                 /* CTYPE: isalpha() */
1360             case 'a':
1361             case 'b':
1362             case 'c':
1363             case 'd':
1364             case 'e':
1365             case 'f':
1366             case 'g':
1367             case 'h':
1368             case 'i':
1369             case 'j':
1370             case 'k':
1371             case 'l':
1372             case 'm':
1373             case 'n':
1374             case 'o':
1375             case 'p':
1376             case 'q':
1377             case 'r':
1378             case 's':
1379             case 't':
1380             case 'u':
1381             case 'v':
1382             case 'w':          /* case 'x': */
1383             case 'y':
1384             case 'z':
1385 
1386             case 'A':
1387             case 'B':
1388             case 'C':
1389             case 'D':
1390             case 'E':
1391             case 'F':
1392             case 'G':
1393             case 'H':
1394             case 'I':
1395             case 'J':
1396             case 'K':
1397             case 'L':
1398             case 'M':
1399             case 'N':
1400             case 'O':
1401             case 'P':
1402             case 'Q':
1403             case 'R':
1404             case 'S':
1405             case 'T':
1406             case 'U':
1407             case 'V':
1408             case 'W':          /* case 'X': */
1409             case 'Y':
1410             case 'Z':
1411                 if (!isalpha((unsigned char)*PrePtr) && (*PrePtr != '\\') && MathMode)
1412                 {
1413                     TmpPtr = BufPtr;
1414                     CmdPtr = CmdBuffer;
1415                     do
1416                     {
1417                         *CmdPtr++ = Char;
1418                         Char = *TmpPtr++;
1419                     }
1420                     while (isalpha((unsigned char)Char));
1421 
1422                     *CmdPtr = 0;
1423 
1424                     if (HasWord(CmdBuffer, &MathRoman))
1425                         HEREA(strlen(CmdBuffer), emWordCommand, CmdBuffer);
1426                 }
1427 
1428                 break;
1429             case ' ':
1430                 TmpPtr = BufPtr;
1431                 SKIP_AHEAD(TmpPtr, TmpC, LATEX_SPACE(TmpC));
1432 
1433                 if (*TmpPtr && *PrePtr)
1434                 {
1435                     if ((TmpPtr - BufPtr) > 0)
1436                     {
1437                         HERE(TmpPtr - BufPtr + 1, emMultiSpace);
1438                         strwrite(BufPtr, VerbClear, TmpPtr - BufPtr - 1);
1439                     }
1440                 }
1441                 break;
1442 
1443             case '.':
1444                 if ((Char == *BufPtr) && (Char == BufPtr[1]))
1445                 {
1446                     const char *cTmpPtr;
1447                     dotlev = CheckDots(&PrePtr[1], &BufPtr[2]);
1448                     cTmpPtr = Dot2Str(dotlev);
1449                     HEREA(3, emEllipsis, cTmpPtr);
1450                 }
1451 
1452                 /* Regexp: "([^A-Z@.])\.[.!?:]*\s+[a-z]" */
1453 
1454                 TmpPtr = BufPtr;
1455                 SKIP_AHEAD(TmpPtr, TmpC, strchr(LTX_EosPunc, TmpC));
1456                 if (LATEX_SPACE(*TmpPtr))
1457                 {
1458                     if (!isupper((unsigned char)*PrePtr) && (*PrePtr != '@') &&
1459                         (*PrePtr != '.'))
1460                     {
1461                         SKIP_AHEAD(TmpPtr, TmpC, LATEX_SPACE(TmpC));
1462                         if (islower((unsigned char)*TmpPtr))
1463                             PSERR(BufPtr - Buf, 1, emInterWord);
1464                         else
1465                             CheckAbbrevs(&BufPtr[-1]);
1466                     }
1467                 }
1468 
1469                 /* FALLTHRU */
1470             case ':':
1471             case '?':
1472             case '!':
1473             case ';':
1474                 /* Regexp: "[A-Z][A-Z][.!?:;]\s+" */
1475 
1476                 if (isspace((unsigned char)*BufPtr) && isupper((unsigned char)*PrePtr) &&
1477                     (isupper((unsigned char)PrePtr[-1]) || (Char != '.')))
1478                     HERE(1, emInterSent);
1479 
1480                 /* FALLTHRU */
1481             case ',':
1482                 if (isspace((unsigned char)*PrePtr) &&
1483                     !(isdigit((unsigned char)*BufPtr) &&
1484                       ((BufPtr[-1] == '.') || (BufPtr[-1] == ','))))
1485                     PSERR(PrePtr - Buf, 1, emSpacePunct);
1486 
1487                 if (MathMode &&
1488                     (((*BufPtr == '$') && (BufPtr[1] != '$')) ||
1489                      (!strafter(BufPtr, "\\)"))))
1490                     HEREA(1, emPunctMath, "outside inner");
1491 
1492                 if (!MathMode &&
1493                     (((*PrePtr == '$') && (PrePtr[-1] == '$')) ||
1494                      (!strinfront(PrePtr, "\\]"))))
1495                     HEREA(1, emPunctMath, "inside display");
1496 
1497                 break;
1498             case '\'':
1499             case '`':
1500                 if ((Char == *BufPtr) && (Char == BufPtr[1]))
1501                 {
1502                     PrintError(CurStkName(&InputStack), RealBuf,
1503                                BufPtr - Buf - 1, 3, Line,
1504                                emThreeQuotes,
1505                                Char, Char, Char, Char, Char, Char);
1506                 }
1507 
1508                 if (Char == '\'')
1509                     MatchC = '`';
1510                 else
1511                     MatchC = '\'';
1512 
1513                 TmpPtr = BufPtr;
1514                 SKIP_AHEAD(TmpPtr, TmpC, TmpC == Char);
1515 
1516                 MixingQuotes = FALSE;
1517 
1518                 if ((*TmpPtr == MatchC) || (*TmpPtr == '\"') ||
1519                     (*TmpPtr == '\xB4')) /* xB4 = latin1 acute accent */
1520                     MixingQuotes = TRUE;
1521 
1522                 SKIP_AHEAD(TmpPtr, TmpC, strchr("`\'\"\xB4", TmpC)); /* xB4 = latin1 acute accent */
1523 
1524                 if (MixingQuotes)
1525                     HERE(TmpPtr - BufPtr + 1, emQuoteMix);
1526 
1527                 switch (Char)
1528                 {
1529                 case '\'':
1530                     if (isalpha((unsigned char)*TmpPtr) &&
1531                         (strchr(LTX_GenPunc, *PrePtr) || isspace((unsigned char)*PrePtr)))
1532                         HERE(TmpPtr - BufPtr + 1, emBeginQ);
1533 
1534                     /* Now check quote style */
1535 #define ISPUNCT(ptr) (strchr(LTX_GenPunc, *ptr) && (ptr[-1] != '\\'))
1536 
1537                     /* We ignore all single words/abbreviations in quotes */
1538 
1539                     {
1540                         char *WordPtr = PrePtr;
1541                         SKIP_BACK(WordPtr, TmpC, (isalnum((unsigned char)TmpC) ||
1542                                                   strchr(LTX_GenPunc, TmpC)));
1543 
1544                         if (*WordPtr != '`')
1545                         {
1546                             if (*PrePtr && (Quote != quTrad)
1547                                 && ISPUNCT(PrePtr))
1548                                 PSERRA(PrePtr - Buf, 1,
1549                                        emQuoteStyle, "in front of");
1550 
1551                             if (*TmpPtr && (Quote != quLogic)
1552                                 && ISPUNCT(TmpPtr))
1553                                 PSERRA(TmpPtr - Buf, 1,
1554                                        emQuoteStyle, "after");
1555                         }
1556                     }
1557 
1558                     break;
1559                 case '`':
1560                     if (isalpha((unsigned char)*PrePtr) &&
1561                         (strchr(LTX_GenPunc, *TmpPtr) || isspace((unsigned char)*TmpPtr)))
1562                         HERE(TmpPtr - BufPtr + 1, emEndQ);
1563                     break;
1564                 }
1565                 BufPtr = TmpPtr;
1566                 break;
1567             case '"':
1568                 HERE(1, emUseQuoteLiga);
1569                 break;
1570 
1571             case '\264':             /* � (in Latin-1) */
1572                 HERE(1, emUseOtherQuote);
1573                 break;
1574 
1575             case '_':
1576             case '^':
1577                 if (*PrePtr != '\\')
1578                 {
1579                     TmpPtr = PrePtr;
1580                     SKIP_BACK(TmpPtr, TmpC, LATEX_SPACE(TmpC));
1581 
1582                     CmdLen = 1;
1583 
1584                     switch (*TmpPtr)
1585                     {
1586                         /*{ */
1587                     case '}':
1588                         if (PrePtr[-1] != '\\')
1589                             break;
1590 
1591                         CmdLen++;
1592                         PrePtr--;
1593                         /* FALLTHRU */
1594                         /*[( */
1595                     case ')':
1596                     case ']':
1597                         PSERR(PrePtr - Buf, CmdLen, emEnclosePar);
1598                     }
1599 
1600                     TmpPtr = BufPtr;
1601                     SKIP_AHEAD(TmpPtr, TmpC, LATEX_SPACE(TmpC));
1602 
1603                     ErrPtr = TmpPtr;
1604 
1605                     if (isalpha((unsigned char)*TmpPtr))
1606                         pstcb = &my_isalpha;
1607                     else if (isdigit((unsigned char)*TmpPtr))
1608                         pstcb = &my_isdigit;
1609                     else
1610                         break;
1611 
1612                     while ((*pstcb) (*TmpPtr++))
1613                         ;
1614                     TmpPtr--;
1615 
1616                     if ((TmpPtr - ErrPtr) > 1)
1617                         PSERR(ErrPtr - Buf, TmpPtr - ErrPtr, emEmbrace);
1618                 }
1619                 break;
1620             case '-':
1621                 CheckDash();
1622                 break;
1623             case '\\':         /* Command encountered  */
1624                 BufPtr = GetLTXToken(--BufPtr, CmdBuffer);
1625 
1626                 if (LATEX_SPACE(*PrePtr))
1627                 {
1628                     if (HasWord(CmdBuffer, &Linker))
1629                         PSERR(PrePtr - Buf, 1, emNBSpace);
1630                     if (HasWord(CmdBuffer, &PostLink))
1631                         PSERR(PrePtr - Buf, 1, emFalsePage);
1632                 }
1633 
1634                 if (LATEX_SPACE(*BufPtr) && !MathMode &&
1635                     !CheckSilentRegex() &&
1636                     (strlen(CmdBuffer) != 2))
1637                 {
1638                     PSERR(BufPtr - Buf, 1, emSpaceTerm);
1639                 }
1640                 else if ((*BufPtr == '\\') && (!isalpha((unsigned char)BufPtr[1])) &&
1641                          (!LATEX_SPACE(BufPtr[1])))
1642                     PSERR(BufPtr - Buf, 2, emNotIntended);
1643 
1644                 PerformBigCmd(PrePtr + 1);
1645                 BufPtr = SkipVerb();
1646 
1647                 break;
1648 
1649             case '(':
1650                 if (*PrePtr && !LATEX_SPACE(*PrePtr) && !isdigit((unsigned char)*PrePtr)
1651                     && !strchr("([{`~", *PrePtr))
1652                 {
1653                     if (PrePtr[-1] != '\\')     /* Short cmds */
1654                     {
1655                         TmpPtr = PrePtr;
1656                         SKIP_BACK(TmpPtr, TmpC, istex(TmpC));
1657                         if (*TmpPtr != '\\')    /* Long cmds */
1658                             PSERRA(BufPtr - Buf - 1, 1, emSpaceParen,
1659                                    "in front of");
1660                     }
1661                 }
1662                 if (isspace((unsigned char)*BufPtr))
1663                     PSERRA(BufPtr - Buf, 1, emNoSpaceParen, "after");
1664                 HandleBracket(Char);
1665                 break;
1666 
1667             case ')':
1668                 if (isspace((unsigned char)*PrePtr))
1669                     PSERRA(BufPtr - Buf - 1, 1, emNoSpaceParen,
1670                            "in front of");
1671                 if (isalpha((unsigned char)*BufPtr))
1672                     PSERRA(BufPtr - Buf, 1, emSpaceParen, "after");
1673                 HandleBracket(Char);
1674                 break;
1675 
1676             case '}':
1677             case '{':
1678             case '[':
1679             case ']':
1680                 HandleBracket(Char);
1681                 break;
1682             case '$':
1683                 if (*PrePtr != '\\')
1684                 {
1685                     if (*BufPtr == '$')
1686                         BufPtr++;
1687                     MathMode ^= TRUE;
1688                 }
1689 
1690                 break;
1691             }
1692         }
1693 
1694         if (!VerbMode)
1695         {
1696             CheckRest();
1697         }
1698 
1699     }
1700 
1701     /* Free and reset LineCpy if it was used */
1702     if ( LineCpy != NULL )
1703     {
1704         free(LineCpy);
1705         LineCpy = NULL;
1706     }
1707     return (TRUE);
1708 }
1709 
1710 /*
1711  * Tries to create plural forms for words. Put a '%s' where a
1712  * suffix should be put, e.g. "warning%s". Watch your %'s!
1713  */
1714 
Transit(FILE * fh,unsigned long Cnt,const char * Str)1715 static void Transit(FILE * fh, unsigned long Cnt, const char *Str)
1716 {
1717     switch (Cnt)
1718     {
1719     case 0:
1720         fputs("No ", fh);
1721         fprintf(fh, Str, "s");
1722         break;
1723     case 1:
1724         fputs("One ", fh);
1725         fprintf(fh, Str, "");
1726         break;
1727     default:
1728         fprintf(fh, "%ld ", Cnt);
1729         fprintf(fh, Str, "s");
1730         break;
1731     }
1732 }
1733 
1734 /*
1735  * Prints the status/conclusion after doing all the testing, including
1736  * bracket stack status, math mode, etc.
1737  */
1738 
PrintStatus(unsigned long Lines)1739 void PrintStatus(unsigned long Lines)
1740 {
1741     unsigned long Cnt;
1742     struct ErrInfo *ei;
1743 
1744 
1745     while ((ei = PopErr(&CharStack)))
1746     {
1747         PrintError(ei->File, ei->LineBuf, ei->Column,
1748                    ei->ErrLen, ei->Line, emNoMatchC, (char *) ei->Data);
1749         FreeErrInfo(ei);
1750     }
1751 
1752     while ((ei = PopErr(&EnvStack)))
1753     {
1754         PrintError(ei->File, ei->LineBuf, ei->Column,
1755                    ei->ErrLen, ei->Line, emNoMatchC, (char *) ei->Data);
1756         FreeErrInfo(ei);
1757     }
1758 
1759     if (MathMode)
1760     {
1761         PrintError(CurStkName(&InputStack), "", 0L, 0L, Lines, emMathStillOn);
1762     }
1763 
1764     for (Cnt = 0L; Cnt < (NUMBRACKETS >> 1); Cnt++)
1765     {
1766         if (Brackets[Cnt << 1] != Brackets[(Cnt << 1) + 1])
1767         {
1768             PrintError(CurStkName(&InputStack), "", 0L, 0L, Lines,
1769                        emNoMatchCC,
1770                        BrOrder[Cnt << 1], BrOrder[(Cnt << 1) + 1]);
1771         }
1772     }
1773 
1774     if (!Quiet)
1775     {
1776         Transit(stderr, ErrPrint, "error%s printed; ");
1777         Transit(stderr, WarnPrint, "warning%s printed; ");
1778         Transit(stderr, UserSupp, "user suppressed warning%s; ");
1779         Transit(stderr, LineSupp, "line suppressed warning%s.\n");
1780 
1781         /* Print how to suppress warnings. */
1782         if ( ErrPrint + WarnPrint > 0 ) {
1783             fprintf(stderr, "See the manual for how to suppress some or all of these warnings/errors.\n" );
1784         }
1785     }
1786 }
1787 
1788 
1789 
1790 /*
1791  * Uses OutputFormat. Be sure that `String'
1792  * does not contain tabs, newlines, etc.
1793  * Prints a formatted string. Formatting codes understood:
1794  *  %b  - string to print Between fields (from -s option)
1795  *  %c  - Column position of error
1796  *  %d  - lenght of error (Digit)
1797  *  %f  - current Filename
1798  *  %i  - Turn on inverse printing mode.
1799  *  %I  - Turn off inverse printing mode.
1800  *  %k  - Kind of error (warning, error, message)
1801  *  %l  - Line number of error
1802  *  %m  - warning Message
1803  *  %n  - warning Number
1804  *  %u  - an Underlining line (like the one which appears when using -v1)
1805  *  %r  - part of line in front of error ('S' - 1)
1806  *  %s  - part of line which contains error (String)
1807  *  %t  - part of line after error ('S' + 1)
1808  */
1809 
1810 
1811 void
PrintError(const char * File,const char * String,const long Position,const long Len,const long LineNo,const enum ErrNum Error,...)1812 PrintError(const char *File, const char *String,
1813            const long Position, const long Len,
1814            const long LineNo, const enum ErrNum Error, ...)
1815 {
1816     static                      /* Just to reduce stack usage... */
1817     char PrintBuffer[BUFSIZ];
1818     va_list MsgArgs;
1819 
1820     char *LastNorm = OutputFormat;
1821     char *of;
1822     int c;
1823 
1824     enum Context Context;
1825 
1826     if (betw(emMinFault, Error, emMaxFault))
1827     {
1828         switch (LaTeXMsgs[Error].InUse)
1829         {
1830         case iuOK:
1831             if (SUPPRESSED_ON_LINE(Error))
1832             {
1833                 LineSupp++;
1834             }
1835             else
1836             {
1837                 Context = LaTeXMsgs[Error].Context;
1838 
1839                 if (!HeadErrOut)
1840                     Context |= ctOutHead;
1841 
1842 #define RGTCTXT(Ctxt, Var) if((Context & Ctxt) && !(Var)) break;
1843 
1844                 RGTCTXT(ctInMath, MathMode);
1845                 RGTCTXT(ctOutMath, !MathMode);
1846                 RGTCTXT(ctInHead, InHeader);
1847                 RGTCTXT(ctOutHead, !InHeader);
1848 
1849                 switch (LaTeXMsgs[Error].Type)
1850                 {
1851                 case etWarn:
1852                     WarnPrint++;
1853                     break;
1854                 case etErr:
1855                     ErrPrint++;
1856                     break;
1857                 case etMsg:
1858                     break;
1859                 }
1860 
1861                 while ((of = strchr(LastNorm, '%')))
1862                 {
1863                     c = *of;
1864                     *of = 0;
1865 
1866                     fputs(LastNorm, OutputFile);
1867 
1868                     *of++ = c;
1869 
1870                     switch (c = *of++)
1871                     {
1872                     case 'b':
1873                         fputs(Delimit, OutputFile);
1874                         break;
1875                     case 'c':
1876                         /* TODO: need to add the offset of the column
1877                          * here when long lines are broken. */
1878                         fprintf(OutputFile, "%ld", Position + 1);
1879                         break;
1880                     case 'd':
1881                         fprintf(OutputFile, "%ld", Len);
1882                         break;
1883                     case 'f':
1884                         fputs(File, OutputFile);
1885                         break;
1886                     case 'i':
1887                         fputs(ReverseOn, OutputFile);
1888                         break;
1889                     case 'I':
1890                         fputs(ReverseOff, OutputFile);
1891                         break;
1892                     case 'k':
1893                         switch (LaTeXMsgs[Error].Type)
1894                         {
1895                         case etWarn:
1896                             fprintf(OutputFile, "Warning");
1897                             break;
1898                         case etErr:
1899                             fprintf(OutputFile, "Error");
1900                             break;
1901                         case etMsg:
1902                             fprintf(OutputFile, "Message");
1903                             break;
1904                         }
1905                         break;
1906                     case 'l':
1907                         fprintf(OutputFile, "%ld", LineNo);
1908                         break;
1909                     case 'm':
1910                         va_start(MsgArgs, Error);
1911                         vfprintf(OutputFile,
1912                                  LaTeXMsgs[Error].Message, MsgArgs);
1913                         va_end(MsgArgs);
1914                         break;
1915                     case 'n':
1916                         fprintf(OutputFile, "%d", Error);
1917                         break;
1918                     case 'u':
1919                         sfmemset(PrintBuffer, ' ', (long) Position);
1920 
1921                         sfmemset(&PrintBuffer[Position], '^', Len);
1922                         PrintBuffer[Position + Len] = 0;
1923                         fputs(PrintBuffer, OutputFile);
1924                         break;
1925                     case 'r':
1926                         substring(String, PrintBuffer, 0L, Position);
1927                         fputs(PrintBuffer, OutputFile);
1928                         break;
1929                     case 's':
1930                         substring(String, PrintBuffer, Position, Len);
1931                         fputs(PrintBuffer, OutputFile);
1932                         break;
1933                     case 't':
1934                         substring(String, PrintBuffer,
1935                                   Position + Len, LONG_MAX);
1936                         fputs(PrintBuffer, OutputFile);
1937                         break;
1938                     default:
1939                         fputc(c, OutputFile);
1940                         break;
1941                     }
1942                     LastNorm = of;
1943                 }
1944                 fputs(LastNorm, OutputFile);
1945             }
1946             break;
1947         case iuNotUser:
1948             UserSupp++;
1949             break;
1950         case iuNotSys:
1951             break;
1952         }
1953     }
1954 }
1955 
1956 /*
1957  * All commands isolated is routed through this command, so we can
1958  * update global statuses like math mode and whether @ is a letter
1959  * or not.
1960  */
1961 
PerformCommand(const char * Cmd,char * Arg)1962 static enum ErrNum PerformCommand(const char *Cmd, char *Arg)
1963 {
1964     const char *Argument = "";
1965     enum ErrNum en = emMinFault;
1966     int TmpC;
1967 
1968     if (!strcmp(Cmd, "\\makeatletter"))
1969         AtLetter = TRUE;
1970     else if (!strcmp(Cmd, "\\makeatother"))
1971         AtLetter = FALSE;
1972     else if (InputFiles && !(strcmp(Cmd, "\\input") && strcmp(Cmd, "\\include")))
1973     {
1974         SKIP_AHEAD(Arg, TmpC, LATEX_SPACE(TmpC));
1975         if (*Arg == '{')        /* } */
1976         {
1977             if (GetLTXArg(Arg, TmpBuffer, GET_STRIP_TOKEN, NULL))
1978                 Argument = TmpBuffer;
1979         }
1980         else
1981             Argument = strip(Arg, STRP_BTH);
1982 
1983         if (!(Argument && PushFileName(Argument, &InputStack)))
1984             en = emNoCmdExec;
1985     }
1986     else if (HasWord(Cmd, &Primitives))
1987         en = emTeXPrim;
1988     else if (HasWord(Cmd, &MathCmd))
1989     {
1990         SKIP_AHEAD(Arg, TmpC, LATEX_SPACE(TmpC));
1991         if (*Arg == '{')
1992         {
1993             MathFlag = MathMode ? efMath : efNoMath;
1994             MathMode = 1;
1995         }
1996     }
1997     else if (HasWord(Cmd, &TextCmd))
1998     {
1999         SKIP_AHEAD(Arg, TmpC, LATEX_SPACE(TmpC));
2000         if (*Arg == '{')
2001         {
2002             MathFlag = MathMode ? efMath : efNoMath;
2003             MathMode = 0;
2004         }
2005     }
2006     else if (*Cmd == '\\')
2007     {
2008         /* Quicker check of single lettered commands. */
2009         switch (Cmd[1])
2010         {
2011         case '(':
2012         case '[':
2013             MathMode = TRUE;
2014             break;
2015         case ']':
2016         case ')':
2017             MathMode = FALSE;
2018             break;
2019         case '/':
2020             switch (ItState)
2021             {
2022             case itOn:
2023                 ItState = itCorrected;
2024                 Argument = Arg;
2025 
2026                 SKIP_AHEAD(Argument, TmpC, TmpC == '{' || TmpC == '}');
2027 
2028                 if (strchr(".,", *Argument))
2029                     en = emItPunct;
2030 
2031                 break;
2032             case itCorrected:
2033                 en = emItDup;
2034                 break;
2035             case itOff:
2036                 en = emItInNoIt;
2037             }
2038             break;
2039         }
2040     }
2041 
2042     return (en);
2043 }
2044