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