/* $Id: lacheck.l 63198 2022-05-01 15:18:17Z karl $
*
* lacheck.lex - A consistency checker checker for LaTeX documents
*
* Copyright (C) 1991, 1992 Kresten Krab Thorup.
* Copyright (C) 1993 --- 1998 Per Abrahamsen.
* Copyright (C) 2019 --- 2022 Karl Berry.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 1, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* original Revision: 1.26 $
* Author : Kresten Krab Thorup
* Created On : Sun May 26 18:11:58 1991
*
* HISTORY
* 02-May-2022 Karl Berry
* Count initial { of definitions, and support \newcommand* (etc.).
* Report from Michael Clark,
* https://tug.org/pipermail/tex-live/2022-April/048096.html.
* Version number now 1.30.
* 08-Jul-2019 Karl Berry
* Separate patterns for \def vs. \newcommand. Report from Zayd Hammoudeh,
* https://tug.org/pipermail/tex-live/2019-January/043083.html.
* Update version number (now 1.29), contact info.
* 07-Mar-1998 Per Abrahamsen
* Added return to yywrap. Patch by Fabrice POPINEAU
* .
* 14-Jan-1998 Per Abrahamsen
* Added GPL blurp.
* 27-Oct-1997 Per Abrahamsen
* Count newline after newenvironment and newcommand.
* 12-Jan-1996 Per Abrahamsen
* \\} used not to end a group in definitions. Reported by Piet
* van Oostrum .
* 03-Jan-1995 Per Abrahamsen
* Fix bug which prevented detection of multiple illegal characters
* in labels. Reported by eeide@jaguar.cs.utah.edu (Eric Eide).
* 30-Jul-1994 Per Abrahamsen
* Define dummy yywrap so we no longer depend on `libl.a'.
* 26-Apr-1994 Per Abrahamsen
* Removed a few warnings, by Richard Lloyd .
* 23-Apr-1994 Per Abrahamsen
* Changed all `%i' to `%d' for VMS portability. Reported by
* Stephen Harker .
* 16-Feb-1994 Per Abrahamsen
* Try file name with `.tex' appended before trying it bare. This
* will make the case where a directory and a TeX file share the
* same name work.
* 19-Jan-1994 Per Abrahamsen
* Comments don't imply whitespace. Pointed out by Jacco van
* Ossenbruggen .
* 14-Jan-1994 Per Abrahamsen
* Don't complain about \ref at the beginning of a paragraph.
* Suggested by Jean-Marc Lasgouttes .
* 11-Jan-1994 Per Abrahamsen
* Added version string to usage message. Suggested by Uwe Bonnes
* .
* 04-Jan-1994 Per Abrahamsen
* Warn about newlines in \verb. Suggested by Mark Burton
* . The LaTeX Book agrees (p. 168).
* 10-Sep-1993 Per Abrahamsen
* Removed complain about missing ~ before \cite. Requested by
* Nelson H. F. Beebe . The LaTeX Book seems
* to agree.
* 03-Sep-1993 Per Abrahamsen
* Check for illegal characters in labels.
* 16-Aug-1993 Per Abrahamsen
* Recognize \endinput. Suggested by Stefan Farestam
* .
* 13-Aug-1993 Per Abrahamsen
* } was eaten after display math. Reported by Eckhard Rüggeberg
* .
* 13-Aug-1993 Per Abrahamsen
* Recognize \verb*. Reported by Eckhard Rüggeberg
* .
* 08-Aug-1993 Per Abrahamsen
* Better catch begin and end without arguments.
* 08-Aug-1993 Per Abrahamsen
* Removed free(NULL) as reported by Darrel R. Hankerson
* .
* 08-Aug-1993 Per Abrahamsen
* Removed declaration of realloc for some C compilers. Reported by
* Darrel R. Hankerson
* 30-Jul-1993 Per Abrahamsen
* Added check for italic correction after normal text.
* 29-Jul-1993 Per Abrahamsen
* Added cast for (char*) malloc as suggested by John Interrante
* .
* 29-Jul-1993 Per Abrahamsen
* Added check for missing and extra italic correction.
* 29-Jul-1993 Per Abrahamsen
* Made line number counting more reliable (but it still needs a rewrite)!
* 28-Jul-1993 Per Abrahamsen
* Added check for italic correction before point or comma.
* 6-Jun-1992 Kresten Krab Thorup
* Last Modified: Sat Jun 6 16:37:44 1992 #48 (Kresten Krab Thorup)
* Added test for whitespace before punctuation mark
* 17-Dec-1991 (Last Mod: Tue Dec 17 21:01:24 1991 #41) Kresten Krab Thorup
* Added 'word word` and missing ~ before cite and ref
* 18-Jun-1991 (Last Mod: Tue Jun 18 19:20:43 1991 #17) Kresten Krab Thorup
* Added check (or rather management) for \newenvironment and
* \newcommand - as suggested by Per Abrahamsen abrham@hugin.dk
* 30-May-1991 (Last Mod: Thu May 30 02:22:33 1991 #15) Kresten Krab Thorup
* Added check for `$${punct}' and `{punct}$' constructions
* 30-May-1991 (Last Mod: Wed May 29 10:31:35 1991 #6) Kresten Krab Thorup
* Improved (dynamic) stack management from Andreas Stolcke ...
*
* 26-May-1991 Kresten Krab Thorup
* Initial distribution version.
*/
%option never-interactive
%{
#include
#include
/* #include */
/* extern char *realloc(); */
#ifdef NEED_STRSTR
char *strstr();
#endif
#define GROUP_STACK_SIZE 10
#define INPUT_STACK_SIZE 10
#define PROGNAME "LaCheck"
/* macros */
#define CG_NAME gstack[gstackp-1].s_name
#define CG_TYPE gstack[gstackp-1].s_type
#define CG_LINE gstack[gstackp-1].s_line
#define CG_ITALIC gstack[gstackp-1].italic
#define CG_FILE gstack[gstackp-1].s_file
void pop(void);
void push(const char *p_name, int p_type, int p_line);
void linecount(void);
void g_checkend(int n);
void e_checkend(int n, char *name);
void f_checkend(char *name);
void input_file(char *file_nam);
void print_bad_match(char *end_command, int type);
int check_top_level_end(char *end_command, int type);
/* global variables */
int line_count = 1;
int warn_count = 0;
char *file_name;
char verb_char;
/* the group stack */
typedef struct tex_group
{
char *s_name;
int s_type;
int s_line;
int italic;
char *s_file;
} tex_group;
tex_group *gstack;
int gstack_size = GROUP_STACK_SIZE;
int gstackp = 0;
typedef struct input_
{
YY_BUFFER_STATE stream;
char *name;
int linenum;
} input_;
input_ *istack;
int istack_size = INPUT_STACK_SIZE;
int istackp = 0;
int def_count = 0;
%}
%x B_ENVIRONMENT E_ENVIRONMENT VERBATIM INCLUDE MATH COMMENT VERB DEF
%x AFTER_DISPLAY ENV_DEF ICOR GETICOR
b_group ("{"|\\bgroup)
e_group ("}"|\\egroup)
b_math \\\(
e_math \\\)
math \$
b_display \\\[
e_display \\\]
display \$\$
par ([ \t]*\n[ \t]*\n[ \t\n]*)
non_par_ws ([ \t]+\n?[ \t]*|[ \t]*\n[ \t]*|[ \t]*\n?[ \t]+)
ws [ \n\t](%[^\n]\n)*
space ({ws}|\~|\\space)
hard_space (\~|\\space)
u_letter [A-ZÆØÅ]
l_letter [a-zæøå]
punct [\!\.\?]
atoz [a-zA-Z]
letter [A-ZÆØÅa-zæøå]
c_bin ("-"|"+"|"\\cdot"|"\\oplus"|"\\otimes"|"\\times")
l_bin (",")
general_abbrev {letter}+{punct}
non_abbrev {u_letter}{u_letter}+{punct}
italic_spec (sl|it)
normal_spec normalshape
swap_spec em
font_spec (rm|bf|{italic_spec}|tt|{swap_spec}|mediumseries|{normal_spec})
primitive \\(above|advance|catcode|chardef|closein|closeout|copy|count|countdef|cr|crcr|csname|delcode|dimendef|dimen|divide|expandafter|font|hskp|vskip|openout)
symbol ("$"("\\"{atoz}+|.)"$"|"\\#"|"\\$"|"\\%"|"\\ref")
%%
<*>"\\\\" { ; }
"\\\%" { ; }
"%"[^\n]*\n { line_count++; }
\n { line_count++; }
"\\\{" { ; }
"\\\}" { ; }
"\\\$" { ; }
"{"{ws} {
if (CG_TYPE != 4 && CG_TYPE != 5) {
if (!(CG_TYPE == 2 && strstr(CG_NAME, "array"))) {
printf( "\"%s\", line %d: possible unwanted space at \"{\"\n",
file_name, line_count);
++warn_count ;
}
}
push( "{", 0, line_count);
linecount();
}
{b_group} { push( "{", 0, line_count);}
{e_group} {
{
int italic = CG_ITALIC;
g_checkend(0);
if (italic && !CG_ITALIC)
BEGIN(GETICOR) ;
else
BEGIN(INITIAL);
}}
{e_group} { g_checkend(0); }
[A-Za-zæøåÆØÅ0-9;:!()]+ {
{
if (!CG_ITALIC)
{
printf("\"%s\", line %d: you may need a \\/ before \"%s\"\n",
file_name, line_count, yytext);
++warn_count;
}
BEGIN(INITIAL);
}}
[A-Za-zæøåÆØÅ0-9;:!?()`']+ {
{
if (CG_ITALIC)
{
printf("\"%s\", line %d: \\/ not needed before italic text \"%s\"\n",
file_name, line_count, yytext);
++warn_count;
}
BEGIN(INITIAL);
}}
^[A-Za-zæøåÆØÅ0-9;:!?()`',.]+{ws}*/\\\/ {
{
linecount();
if (!CG_ITALIC)
{
printf("\"%s\", line %d: \\/ not needed after non-italic text \"%s\"\n",
file_name, line_count, yytext);
++warn_count;
}
}}
{ws}[A-Za-zæøåÆØÅ0-9;:!?()`',.]+{ws}*/\\\/ {
{
linecount();
if (!CG_ITALIC)
{
printf("\"%s\", line %d: \\/ is not needed after non-italic \"%s\"\n",
file_name, line_count, yytext);
++warn_count;
}
}}
\\\/ { BEGIN(INITIAL); }
\\\/ { BEGIN(ICOR); }
\\\/ {
{
printf("\"%s\", line %d: double \\/ found \"%s\"\n",
file_name, line_count, yytext);
++warn_count;
BEGIN(ICOR);
}}
\\{italic_spec}/[^a-zA-Z] { CG_ITALIC = 1; }
\\{normal_spec}/[^a-zA-Z] {
{
if(CG_ITALIC)
BEGIN(GETICOR);
else
BEGIN(INITIAL);
CG_ITALIC = 0;
}}
\\{normal_spec}/[^a-zA-Z] { CG_ITALIC = 0; }
\\{swap_spec}/[^a-zA-Z] {
{
if(CG_ITALIC)
BEGIN(GETICOR);
else
BEGIN(INITIAL);
CG_ITALIC = !CG_ITALIC;
}}
\\{swap_spec}/[^a-zA-Z] { CG_ITALIC = !CG_ITALIC; }
[,.] {
{
printf("\"%s\", line %d: do not use \\/ before \"%s\"\n",
file_name, line_count, yytext);
++warn_count;
BEGIN(INITIAL);
}}
{ws} { ; }
~ { ; }
[^\n] {
{
unput(yytext[0]);
BEGIN(INITIAL);
}}
/* \def (or \edef etc.) can be followed by whitespace, then the \ to
start the control sequence, then more or less anything up to the {
starting the replacement text, which we assume is on the same line.
(All this could be changed in TeX, with catcodes etc., but we can't
try to handle that.) */
"\\"[exg]?def[ \t\n]*"\\"[^\n{]+\{ { ++def_count; BEGIN(DEF); }
/* \newcommand{\foo}{...} or \newcommand\foo{...}
or \renewcommand or \providecommand;
and/or with \...command*;
and/or with \...command[N] argument count;
To allow for arbitrary control sequence names, we match
the braced name as anything not containing a }.
We assume the control sequence name is either a single character or
one or more English letters, a-zA-Z, as usual with TeX.
To handle the [N] argument count and [dflt] optional argument
default specifications, we just match anything after the control
sequence up to the next {, which we assume starts the definition.
We don't do anything with the definition; the only purpose is to
count braces. */
"\\"(provide|(re)?newcommand)[ \t\n]*(\*[ \t\n]*)?(\{\\[^}]+\}|\\([a-zA-Z]+|.))[^{]*\{ { ++def_count; BEGIN(DEF); }
/* ws optional* ({\cs} |\cs) .*? {defn}
Pre-2019 rule was (see test-def.tex):
"\\"[exg]?(def|newcommand)[^\n\{]+ BEGIN(DEF);
*/
{b_group} { ++def_count; }
{e_group} { --def_count;
if(def_count == 0)
BEGIN(INITIAL); }
. { ; }
"\\"newenvironment"{"[a-zA-Z]+"}"[^\n\{]+ BEGIN(ENV_DEF);
{b_group} { ++def_count; }
{e_group} { --def_count;
if(def_count == 0)
BEGIN(DEF); }
. { ; }
{b_math} {
if(CG_TYPE == 4 || CG_TYPE == 5)
print_bad_match(yytext,4);
else
{
push( yytext, 4, line_count);
}}
{e_math} { g_checkend(4); }
{b_display} {
if(CG_TYPE == 4 || CG_TYPE == 5)
print_bad_match(yytext,5);
else
{
push( yytext, 5, line_count);
}}
{e_display} { g_checkend(5); BEGIN(AFTER_DISPLAY);}
{punct} {
printf( "\"%s\", line %d: punctuation mark \"%s\" should be placed before end of displaymath\n",
file_name, line_count, yytext);
++warn_count ;
BEGIN(INITIAL); }
(\n|.) { unput(yytext[0]); BEGIN(INITIAL); }
{punct}/("\$"|"\\)") { if (CG_TYPE == 4)
{
printf( "\"%s\", line %d: punctuation mark \"%s\" should be placed after end of math mode\n",
file_name, line_count, yytext);
++warn_count ;
BEGIN(INITIAL);
}}
{math} {
if(CG_TYPE == 5)
print_bad_match(yytext, 4);
else
if(CG_TYPE == 4)
{
e_checkend(4, yytext);
}
else
{
push( yytext, 4, line_count);
}}
{display} {
if(CG_TYPE == 4)
print_bad_match(yytext,5);
else
if(CG_TYPE == 5)
{
e_checkend(5, yytext);
BEGIN(AFTER_DISPLAY);
}
else
{
push( yytext, 5, line_count);
}}
\\begingroup/[^a-zA-Z] {
{
push("\\begingroup", 1, line_count);
}}
\\endgroup/[^a-zA-Z] {
{
g_checkend(1);
}}
\\begin[ \t]*"{" { BEGIN(B_ENVIRONMENT); }
\\begin[ \t]*(%[^\n]*)?/\n {
{
printf("\"%s\", line %d: {argument} missing for \\begin\n",
file_name, line_count) ;
++warn_count;
}}
[^\}\n]+ {
{
if (strcmp( yytext, "verbatim" ) == 0 )
{
input();
BEGIN(VERBATIM);
}
else
{
push(yytext, 2, line_count);
if ( strcmp (yytext, "sl" ) == 0
|| strcmp (yytext, "it" ) == 0)
CG_ITALIC = 1;
else if (strcmp (yytext, "normalshape") == 0)
CG_ITALIC = 0;
else if (strcmp (yytext, "em") == 0)
CG_ITALIC = !CG_ITALIC;
input();
BEGIN(INITIAL);
}
}}
\\end[ \t]*\{verbatim\} { BEGIN(INITIAL); }
\t {
printf("\"%s\", line %d: TAB character in verbatim environment\n",
file_name, line_count) ;
++warn_count;
}
. { ; }
\n { ++line_count; }
\\verb\*?. {
verb_char = yytext[yyleng-1];
BEGIN(VERB);
}
\n {
printf("\"%s\", line %d: \\verb should not contain end of line characters\n",
file_name, line_count) ;
++line_count;
}
. {
if ( *yytext == verb_char )
BEGIN(INITIAL);
}
\\end[ \t]*"{" { BEGIN(E_ENVIRONMENT); }
\\end[ \t]*(%[^\n]*)?/\n {
{
printf("\"%s\", line %d: {argument} missing for \\end\n",
file_name, line_count) ;
++warn_count;
}}
[^\}\n]+ {
{
e_checkend(2, yytext);
input();
BEGIN(INITIAL);
}}
{ws}({letter}".")*{letter}*{l_letter}"."/{non_par_ws}+{l_letter} {
{
linecount();
printf( "\"%s\", line %d: missing `\\ ' after \"%s\"\n",
file_name, line_count, ++yytext);
++warn_count ;
BEGIN(INITIAL);
}}
({l_letter}".")*{letter}*{l_letter}"."/{non_par_ws}+{l_letter} {
{
printf( "\"%s\", line %d: missing `\\ ' after \"%s\"\n",
file_name, line_count, yytext);
++warn_count ;
BEGIN(INITIAL);
}}
{non_abbrev}/{non_par_ws}{u_letter} {
{
linecount();
printf("\"%s\", line %d: missing `\\@' before `.' in \"%s\"\n",
file_name, line_count, yytext);
++warn_count ;
BEGIN(INITIAL);
}}
({hard_space}{space}|{space}{hard_space}) {
printf("\"%s\", line %d: double space at \"%s\"\n",
file_name, line_count, yytext);
++warn_count;
linecount();
BEGIN(INITIAL);
}
{c_bin}{ws}?(\\(\.|\,|\;|\:))*{ws}?\\ldots{ws}?(\\(\.|\,|\;|\:))*{ws}?{c_bin} {
printf("\"%s\", line %d: \\ldots should be \\cdots in \"%s\"\n",
file_name, line_count, yytext);
++warn_count;
linecount();
}
[^\\]{l_bin}{ws}?(\\(\.|\,|\;|\:))*{ws}?\\cdots{ws}?(\\(\.|\,|\;|\:))*{ws}?[^\\]{l_bin} {
printf("\"%s\", line %d: \\cdots should be \\ldots in \"%s\"\n",
file_name, line_count, yytext);
++warn_count;
linecount();
BEGIN(INITIAL);
}
{c_bin}{ws}?(\\(\.|\,|\;|\:))*{ws}?"."+{ws}?(\\(\.|\,|\;|\:))*{ws}?{c_bin} {
printf("\"%s\", line %d: Dots should be \\cdots in \"%s\"\n",
file_name, line_count, yytext);
++warn_count;
linecount();
}
[^\\]{l_bin}{ws}?(\\(\.|\,|\;|\:))*{ws}?"."+{ws}?(\\(\.|\,|\;|\:))*{ws}?[^\\]{l_bin} {
printf("\"%s\", line %d: Dots should be \\ldots in \"%s\"\n",
file_name, line_count, yytext);
++warn_count;
linecount();
BEGIN(INITIAL);
}
\.\.\. {
printf("\"%s\", line %d: Dots should be ellipsis \"%s\"\n",
file_name, line_count, yytext);
++warn_count;
BEGIN(INITIAL);
}
\\label\{[^#$%^&*+={\~"<>\n\t }]*[#$%^&*+={\~"<>\n\t ][^%}]*\} {
linecount();
printf("\"%s\", line %d: bad character in label \"%s\", see C.10.2\n",
file_name, line_count, yytext);
}
{par}"\\"ref/[^A-Za-z] {
linecount();
BEGIN(INITIAL);
}
{ws}"\\"ref/[^A-Za-z] {
linecount();
printf("\"%s\", line %d: perhaps you should insert a `~' before \"%s\"\n",
file_name, line_count, ++yytext);
BEGIN(INITIAL);
}
{ws}"\\"footnote/[^A-Za-z] {
linecount();
printf("\"%s\", line %d: whitespace before footnote in \"%s\"\n",
file_name, line_count, ++yytext);
BEGIN(INITIAL);
}
{primitive}/[^a-zA-Z] {
{
printf("\"%s\", line %d: Don't use \"%s\" in LaTeX documents\n",
file_name, line_count, yytext);
++warn_count ;
}}
\\left{ws}*\\?. { linecount() ;}
\\right{ws}*\\?. { linecount(); }
[^\{]\\{font_spec}/[ \t]*"{" {
{
linecount();
printf("\"%s\", line %d: Fontspecifiers don't take arguments. \"%s\"\n",
file_name, line_count, yytext);
++warn_count;
/* (void) input(); */
BEGIN(INITIAL);
}}
\\([a-zA-Z\@]+\@[a-zA-Z\@]*|[a-zA-Z\@]*\@[a-zA-Z\@]+) {
{
printf("\"%s\", line %d: Do not use @ in LaTeX macro names. \"%s\"\n",
file_name, line_count, yytext);
++warn_count;
}}
{ws}"'"+{letter}+ {
{
linecount();
printf("\"%s\", line %d: Use ` to begin quotation, not ' \"%s\"\n",
file_name, line_count, yytext);
++warn_count;
BEGIN(INITIAL);
}}
{letter}+"`" {
{
printf("\"%s\", line %d: Use ' to end quotation, not ` \"%s\"\n",
file_name, line_count, yytext);
++warn_count;
BEGIN(INITIAL);
}}
{ws}+{punct} {
{
printf("\"%s\", line %d: Whitespace before punctation mark in \"%s\"\n",
file_name, line_count, yytext);
++warn_count;
linecount();
BEGIN(INITIAL);
}}
"%" { BEGIN(COMMENT); }
\n { BEGIN(INITIAL); ++line_count; }
. { ; }
\\(input|include)([ \t]|"{") { BEGIN(INCLUDE); }
[^\}\n]+ {
{
if ( strstr(yytext,".sty") == NULL )
{
printf("** %s:\n", yytext);
input_file(yytext);
}
else
{
printf("\"%s\", line %d: Style file `%s\' omitted.\n",
file_name,
line_count,
yytext);
input();
}
BEGIN(INITIAL);
}}
\\endinput/[^A-Za-z] |
<> {
if (def_count != 0)
{
printf("\"%s\", line %d: %d missing right brace(s).\n",
file_name,
line_count,
def_count);
}
if (--istackp < 0)
yyterminate();
else
{
fclose(yyin);
f_checkend(file_name);
yy_switch_to_buffer(istack[istackp].stream);
free(file_name);
line_count = istack[istackp].linenum;
file_name = istack[istackp].name;
input();
BEGIN(INITIAL);
}
}
. { ; }
%%
static void print_version (void)
{
printf("%s (TeX Live) %s\n", PROGNAME, "1.30");
puts("$Id: lacheck.l 63198 2022-05-01 15:18:17Z karl $");
printf("License GPLv1+: GNU GPL version 1 or later");
puts(" .");
puts("This is free software: you are free to change and redistribute it.");
puts("There is NO WARRANTY, to the extent permitted by law.");
puts("Written by Kresten Krab Thorup and Per Abrahamsen.");
}
static void print_help (void)
{
printf("Usage: lacheck FILENAME[.tex]\n");
printf("A consistency checker for LaTeX documents.\n\n");
printf("Document context is displayed in \"double quotes\".\n");
printf("All messages are only warnings!\n");
printf("Your document may be right even though LaCheck says");
printf(" otherwise.\n\n");
print_version();
printf("\nEmail bug reports to tex-live@tug.org.\n");
}
int main(int argc, char *argv[])
{
/* allocate initial stacks */
gstack = (tex_group *)malloc(gstack_size * sizeof(tex_group));
istack = (input_ *)malloc(istack_size * sizeof(input_));
if ( gstack == NULL || istack == NULL ) {
fprintf(stderr, "%s: not enough memory for stacks\n", PROGNAME);
exit(3);
}
if (argc == 2)
{
if (strcmp(argv[1], "--help") == 0) {
print_help();
exit(0);
} else if (strcmp(argv[1], "--version") == 0) {
print_version();
exit(0);
}
if ( (file_name = (char*) malloc(strlen(argv[1]) + 5)) == NULL ) {
fprintf(stderr, "%s: out of memory\n", PROGNAME);
exit(3);
}
strcpy(file_name, argv[1]);
strcat(file_name, ".tex" );
if ((yyin = fopen( file_name, "r")) != NULL )
{
push(file_name, 3, 1);
yylex();
f_checkend(file_name);
}
else {
file_name[strlen(file_name) - 4] = '\0';
if ((yyin = fopen( file_name, "r")) != NULL )
{
push(file_name, 3, 1);
yylex();
f_checkend(file_name);
}
else
{
fprintf(stderr, "%s: Could not open ",PROGNAME);
perror(argv[1]);
exit(1);
}
}
/* printf("%s checked.\n", argv[1]); */
}
else
{
printf("Usage: lacheck FILENAME[.tex]\n");
printf("Try 'lacheck --help' for more information.\n");
exit(1);
}
return(0);
}
int yywrap(void) { return 1; }
#ifdef NEED_STRSTR
char *
strstr(string, substring)
register char *string; /* String to search. */
char *substring; /* Substring to try to find in string. */
{
register char *a, *b;
/* First scan quickly through the two strings looking for a
* single-character match. When it's found, then compare the
* rest of the substring.
*/
b = substring;
if (*b == 0) {
return string;
}
for ( ; *string != 0; string += 1) {
if (*string != *b) {
continue;
}
a = string;
while (1) {
if (*b == 0) {
return string;
}
if (*a++ != *b++) {
break;
}
}
b = substring;
}
return (char *) 0;
}
#endif /* NEED_STRSTR */
void push(const char *p_name, int p_type, int p_line)
{
if ( gstackp == gstack_size ) { /* extend stack */
gstack_size *= 2;
gstack = (tex_group *)realloc(gstack, gstack_size * sizeof(tex_group));
if ( gstack == NULL ) {
fprintf(stderr, "%s: stack out of memory", PROGNAME);
exit(3);
}
}
if ( (gstack[gstackp].s_name =
(char *)malloc(strlen(p_name) + 1)) == NULL ||
(gstack[gstackp].s_file =
(char *)malloc(strlen(file_name) + 1)) == NULL ) {
fprintf(stderr, "%s: out of memory\n", PROGNAME);
exit(3);
}
strcpy(gstack[gstackp].s_name, p_name);
gstack[gstackp].s_type = p_type;
gstack[gstackp].s_line = p_line;
gstack[gstackp].italic = ( (p_type == 4 || p_type == 5)
? 1
: ( gstackp
? gstack[gstackp - 1].italic
: 0));
strcpy(gstack[gstackp].s_file,file_name);
++gstackp;
}
void input_file(char *file_nam)
{
char *tmp_file_name;
FILE *tmp_yyin;
if ( (tmp_file_name = (char*) malloc(strlen(file_nam) + 5)) == NULL ) {
fprintf(stderr, "%s: out of memory\n", PROGNAME);
exit(3);
}
strcpy(tmp_file_name,file_nam);
if (istackp == istack_size) { /* extend stack */
istack_size *= 2;
istack = (input_ *)realloc(istack, istack_size * sizeof(input_));
if ( istack == NULL ) {
fprintf(stderr, "%s: \\input stack out of memory\n", PROGNAME);
exit(3);
}
}
istack[istackp].stream = YY_CURRENT_BUFFER;
istack[istackp].linenum = line_count;
istack[istackp].name = file_name;
++istackp;
(void) strcat(tmp_file_name, ".tex");
if ((tmp_yyin = fopen( tmp_file_name, "r")) != NULL )
{
yyin = tmp_yyin;
yy_switch_to_buffer(yy_create_buffer(yyin,YY_BUF_SIZE));
file_name = tmp_file_name;
push(file_name, 3, 1);
line_count = 1;
}
else {
tmp_file_name[strlen(tmp_file_name) - 4] = '\0';
if ((tmp_yyin = fopen( tmp_file_name , "r")) != NULL )
{
yyin = tmp_yyin;
yy_switch_to_buffer(yy_create_buffer(yyin,YY_BUF_SIZE));
file_name = tmp_file_name;
push(file_name, 3, 1);
line_count = 1;
}
else
{
--istackp;
free(tmp_file_name);
printf("\"%s\", line %d: Could not open \"%s\"\n",
file_name,
line_count,
file_nam);
input();
}
}
}
void pop(void)
{
if ( gstackp == 0 )
{
fprintf(stderr, "%s: Stack underflow\n", PROGNAME);
exit(4);
}
--gstackp;
free(gstack[gstackp].s_name);
free(gstack[gstackp].s_file);
}
static void print_bg_command(char *name)
{
switch (CG_TYPE) {
case 2:
printf("\\begin{%s}", name);
break;
case 3:
printf("beginning of file %s", name);
break;
case 4:
printf("math begin %s", name);
break;
case 5:
printf("display math begin %s", name);
break;
default:
printf("%s", name);
}
}
static void print_eg_command(char *name, int type)
{
switch (type) {
case 2:
printf("\\end{%s}", name);
break;
case 3:
printf("end of file %s", name);
break;
case 4:
printf("math end %s", name);
break;
case 5:
printf("display math end %s", name);
break;
default:
printf("%s", name);
break;
}
}
void g_checkend(int n)
{
if ( check_top_level_end(yytext,n) == 1 )
{
if ( CG_TYPE != n )
print_bad_match(yytext,n);
else
pop();
}
}
void e_checkend(int n, char *name)
{
if ( check_top_level_end(name,n) == 1 )
{
if ( CG_TYPE != n || strcmp( CG_NAME, name ) != 0 )
print_bad_match(name,n);
if ( CG_TYPE != 3 )
pop();
}
}
void f_checkend(char *name)
{
if ( check_top_level_end(name,3) == 1 )
{
if ( CG_TYPE != 3 || strcmp( CG_NAME, name ) != 0 )
while( CG_TYPE != 3 )
{
print_bad_match(name,3);
pop();
}
pop();
}
}
void print_bad_match(char *end_command, int type)
{
printf("\"%s\", line %d: <- unmatched \"",
file_name,
line_count);
print_eg_command(end_command, type);
printf("\"\n");
printf("\"%s\", line %d: -> unmatched \"",
CG_FILE,
CG_LINE);
print_bg_command(CG_NAME);
printf("\"\n");
warn_count += 2;
}
int check_top_level_end(char *end_command, int type)
{
if ( gstackp == 0 )
{
printf("\"%s\", line %d: \"",
file_name,
line_count);
print_eg_command(end_command, type);
printf("\" found at top level\n");
++warn_count;
return(0);
}
else
return(1);
}
void linecount(void)
{
int i;
for (i = 0; i < yyleng; i++)
if(yytext[i] == '\n')
line_count++;
}