/*                    -*- Mode: C -*- 
 * 
 * lacheck.lex - A consistency checker checker for LaTeX documents
 * 
 * Copyright (C) 1991 Kresten Krab Thorup (krab@iesd.auc.dk).
 * 
 * $Locker:  $
 * $Revision: 1.1 $
 * Author          : Kresten Krab Thorup
 * Created On      : Sun May 26 18:11:58 1991
 * Last Modified By: Kresten Krab Thorup
 * Last Modified On: Thu May 30 02:29:57 1991
 * Update Count    : 16
 * 
 * HISTORY
 * 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 (dynamical) stack management from Andreas Stolcke ...
 *                                       <stolcke@ICSI.Berkeley.EDU> 
 * 26-May-1991  Kresten Krab Thorup
 *    Initial distribution version.
 */
%{
#include <stdio.h>
#include <string.h>
#include <sys/param.h>

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_FILE gstack[gstackp-1].s_file

char *bg_command();
void pop();
void push();
void g_checkend();
void e_checkend();
void f_checkend();
void input_file();
void print_bad_match();
int check_top_level_end();

  /* global variables */

char returnval[100];
int line_count = 1;
int warn_count = 0;
char *file_name;
char verb_char;

  /* the group stack */

typedef struct tex_group 
 {
    unsigned char *s_name;
    int s_type;
    int s_line;
    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

b_group ("{"|\\bgroup)
e_group ("}"|\\egroup)

b_math \\\(
e_math \\\)
math \$

b_display \\\[
e_display \\\]
display \$\$

non_par_ws ([ \t]+\n?[ \t]*|[ \t]*\n[ \t]*|[ \t]*\n?[ \t]+)

ws [ \n\t]
space ({ws}|\~|\\space)
hard_space (\~|\\space)

u_letter [A-ZFXE] 
l_letter [a-zfxe] 
punct [\!\.\?]
atoz [a-zA-Z]
letter (u_letter|l_letter)

c_bin ("-"|"+"|"\\cdot"|"\\oplus"|"\\otimes"|"\\times")
l_bin (",")

general_abbrev {letter}+{punct}

non_abbrev {u_letter}{u_letter}+{punct}

font_spec (rm|bf|sl|it|tt|em|mediumseries|normalshape)

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")

%%

"\\\\" { ; }

<DEF,INITIAL>"\\\%" { ; }

<DEF,INITIAL>"%"[^\n]* { ; }

<DEF,INITIAL>\n 	{ line_count++; }

<DEF,INITIAL>"\\\{" { ; }

<DEF,INITIAL>"\\\}" { ; }

"\\\$" { ; }

{b_group} {  push( "{", 0, line_count);}

{e_group} {  g_checkend(0); }

"\\"[exg]?def[^\{] 	BEGIN(DEF);

<DEF>{b_group} { ++def_count; }

<DEF>{e_group} { --def_count;
		 if(def_count == 0)
		     BEGIN(INITIAL); }

<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);}

<AFTER_DISPLAY>{punct} { 

    printf( "\"%s\", line %d: puctation mark \"%s\" should be placed before end of displaymath\n", 
	   file_name, line_count, yytext); 
    ++warn_count ; 

  BEGIN(INITIAL); }

<AFTER_DISPLAY>. {  BEGIN(INITIAL); }

<AFTER_DISPLAY>\n { 	++line_count; 
			BEGIN(INITIAL); }

{punct}/("\$"|"\\)") { if (CG_TYPE == 4)
       {
	 printf( "\"%s\", line %d: puctation mark \"%s\" should be placed after end of math mode\n", 
		file_name, line_count, yytext); 
	 ++warn_count ;
       }}

{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((unsigned char *)"\\begingroup", 1, line_count); 
 }}


\\endgroup/[^a-zA-Z]  {
 {
    g_checkend(1);
 }}


\\begin[ \t]*"{" { BEGIN(B_ENVIRONMENT); }

\\begin[ \t]*/\n { 
 {
    
    printf("\"%s\", line %i: {argument} missing for \\begin\n",
	   file_name, line_count) ;
    ++warn_count;
 }}

<B_ENVIRONMENT>[^\}\n]+ { 
 {
    if (strcmp( yytext, "verbatim" ) == 0 )
	{
	 input();
	 BEGIN(VERBATIM);
	}
    else
	{
    	 push(yytext, 2, line_count);
 	 input();
	 BEGIN(INITIAL);
	}
 }}

<VERBATIM>\\end[ \t]*\{verbatim\} { BEGIN(INITIAL); }

<VERBATIM>. { ; }

<VERBATIM>\n { ++line_count; }


\\verb.	{ 
	  sscanf (yytext, "\\verb%c", &verb_char  ); 
	  BEGIN(VERB); 
	}

<VERB>. {
		if ( *yytext == verb_char )
		  BEGIN(INITIAL); 
		if ( *yytext == '\n' )
 		  ++line_count;
	} 


\\end[ \t]*"{" { BEGIN(E_ENVIRONMENT); }

\\end[ \t]*/\n { 
 {
    printf("\"%s\", line %i: {argument} missing for \\end\n",
	   file_name, line_count) ;
    ++warn_count;
 }}


<E_ENVIRONMENT>[^\}\n]+ { 
 {
    e_checkend(2, yytext);
    input();
    
    BEGIN(INITIAL);
 }}


{ws}([a-zfxe]".")*[a-zA-ZfxeFXE]*[a-zfxe]"."/{non_par_ws}+[a-zfxe]    { 
 {
    if ( *yytext == '\n' )
		++line_count; 

    printf( "\"%s\", line %d: missing `\\ ' after \"%s\"\n", 
	   file_name, line_count, ++yytext); 
    ++warn_count ; 
 }}

([a-zfxe]".")*[a-zA-ZfxeFXE]*[a-zfxe]"."/{non_par_ws}+[a-zfxe]    { 
 {
    printf( "\"%s\", line %d: missing `\\ ' after \"%s\"\n", 
	   file_name, line_count, yytext); 
    ++warn_count ; 
 }}

{ws}{non_abbrev}/{non_par_ws}{u_letter}   { 
 {
    if ( *yytext == '\n' )
	++line_count;
    printf("\"%s\", line %d: missing `\\\@' before punctation mark in \"%s\"\n", 
	   file_name, line_count, ++yytext); 
    ++warn_count ; 
 }}

{non_abbrev}/{non_par_ws}{u_letter}   { 
 {
    printf("\"%s\", line %d: missing `\\\@' before `.' in \"%s\"\n", 
	   file_name, line_count, yytext); 
    ++warn_count ; 
 }}

({hard_space}{space}|{space}{hard_space})  { 

    printf("\"%s\", line %d: double space at \"%s\"\n",
	   file_name, line_count, yytext); 
    ++warn_count;
  }

{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;
  }

[^\\]{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;
  }

{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;
  }

[^\\]{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;
  }


\.\.\. { 
    printf("\"%s\", line %d: Dots should be ellipsis \"%s\"\n",
	   file_name, line_count, yytext); 
    ++warn_count;
  }

 /*
 *
 *  The `~' one is not too good, perhaps it shoud be an option.
 *
 */
 /*
 {l_letter}" "{symbol}  {

    printf("\"%s\", line %d: perhaps you should insert a `~' before%s\n",
	   file_name, line_count, ++yytext); 
  }
 */
 
 /*
 {primitive}/[^a-zA-Z] {
 {
    printf("\"%s\", line %d: Don't use \"%s\" in LaTeX documents\n", 
	   file_name, line_count, yytext); 
    ++warn_count ; 
 }}    
 */

\\{font_spec}/[ \t]*"{" { 
 {
    printf("\"%s\", line %d: Fontspecifiers don't take arguments. \"%s\"\n", 
	   file_name, line_count, yytext); 
    ++warn_count; 
  /*    (void) input(); */
 }}


\\([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; 
 }}


"%"  { BEGIN(COMMENT); }

<COMMENT>\n	{ BEGIN(INITIAL); ++line_count; }

<COMMENT>.	{ ; }


\\(input|include)([ \t]|"{")	{ BEGIN(INCLUDE); }

<INCLUDE>[^\}\n]+	{
 {
	if ( strstr(yytext,"\.sty") == NULL )
	{
		input_file(yytext);
	}
	else
	{
		printf("\"%s\", line %d: Style file \`%s\' omitted.\n",
			file_name,
			line_count,
			yytext);
		input();
	}
	BEGIN(INITIAL);
 }}

<<EOF>> { 
	  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);
		}    	
	 
	}


. { ; }
%%
int main( argc, argv )
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 > 1)
    {
        if ( (file_name = malloc(strlen(argv[1]) + 5)) == NULL ) {
		fprintf(stderr, "%s: out of memory\n", PROGNAME);
		exit(3);
	}
	
	strcpy(file_name, argv[1]);
	
	if ((yyin = fopen( file_name, "r")) != NULL )
	{
	    push(file_name, 3, 1);
	    yylex();
	    f_checkend(file_name);
	}
	else {   
		 strcat(file_name, ".tex" );
		 if ((yyin = fopen( file_name, "r")) != NULL )
		 {
		     push(file_name, 3, 1);
		     yylex();
		     f_checkend(file_name);
		 }
		 else
		     fprintf(stderr,
				"%s: Could not open : %s\n",PROGNAME, argv[1]);
	     }
    }
    else
    {
	printf("\n* %s *\n\n",PROGNAME);
	printf("\t...a consistency checker for LaTeX documents.\n\n");

	printf("Usage:\n\tlacheck filename[.tex] <return>\n\n\n");

	printf("\tFrom within Emacs:\n\n\t");
	printf("M-x compile <return>\n\tlacheck filename[.tex] <return>");
	printf("\n\n\tUse C-x ` to step through the messages.\n\n");
	printf("\n\tThe found context is displayed in \"double quotes\"\n\n");
	printf("Remark:\n\tAll messages are only warnings!\n\n");
	printf("\tYour document may be right though LaCheck tells\n");
	printf("\tsomthing else.\n\n");
    }
    return(0);
}

#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(p_name, p_type, p_line)
unsigned 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 =
		(unsigned 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;	
    strcpy(gstack[gstackp].s_file,file_name);
    ++gstackp;	

}

void input_file(file_nam)
char *file_nam;
{
    char *tmp_file_name;
    FILE *tmp_yyin;

    if ( (tmp_file_name = 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;    

    if ((tmp_yyin = fopen( file_nam, "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 {
	  (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
	  {
	       --istackp;
	       fclose(tmp_yyin);
	       free(tmp_file_name);
	       printf("\"%s\", line %d: Could not open \"%s\"\n", 
			file_name,
			line_count,
			file_nam);
	       input();
	  }
	 }
}

void pop()
{
    if ( gstackp == 0 )
    {
       	fprintf(stderr, "%s: Stack underflow\n", PROGNAME);
	exit(4);
    }
    --gstackp;

    free(gstack[gstackp].s_name);
    free(gstack[gstackp].s_file);
}

char *bg_command(name)
char *name;
{
    
    switch (CG_TYPE) {
	
    case 2:
	(void) strcpy( returnval, "\\begin\{" );
	(void) strcat( returnval, (char *) name);
	(void) strcat( returnval, "}" );
	break;
	
    case 3:
	(void) strcpy( returnval, "beginning of file " );
	(void) strcat( returnval, (char *) name);
	break;
    
    case 4:
	(void) strcpy( returnval, "math begin " );
	(void) strcat( returnval, (char *) name);
	break;
    
    case 5:
	(void) strcpy( returnval, "display math begin " );
	(void) strcat( returnval, (char *) name);
	break;
    
    default:
    	(void) strcpy( returnval, name );
	
    }
    
    return ((char *)returnval);
}

char *eg_command(name,type)
int type;
char *name;
{
    
    switch (type) {
	
    case 2:
	(void) strcpy( returnval, "\\end{" );
	(void) strcat( returnval, (char *) name);
	(void) strcat( returnval, "}" );
	break;
	
    case 3:
	(void) strcpy( returnval, "end of file " );
	(void) strcat( returnval, (char *) name);
	break;
    
    case 4:
	(void) strcpy( returnval, "math end " );
	(void) strcat( returnval, (char *) name);
	break;
    
    case 5:
	(void) strcpy( returnval, "display math end " );
	(void) strcat( returnval, (char *) name);
	break;
    
    default:
    	(void) strcpy( returnval, name );
	break;
    }
    
    return ((char *)returnval);
}


void g_checkend(n)
int n;
{
    if ( check_top_level_end(yytext,n) == 1 ) 
       if (  CG_TYPE != n  )
	 print_bad_match(yytext,n);
       else
	pop();
}

void e_checkend(n, name)
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);

     pop();

    }
}

void f_checkend(name)
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(end_command,type)
char *end_command;	      
int type;
{
	  printf("\"%s\", line %i: <- unmatched \"%s\"\n",
	         file_name, 
		 line_count, 
		 eg_command( end_command , type) ) ;

	  printf("\"%s\", line %i: -> unmatched \"%s\"\n",
	         CG_FILE, 
		 CG_LINE, 
		 bg_command( CG_NAME ) ) ;
	  warn_count += 2;
}

int check_top_level_end(end_command,type)
char *end_command;	      
int type;
{
    if ( gstackp == 0 )
	{
	 printf("\"%s\", line %i: \"%s\" found at top level\n",
	       file_name, 
	       line_count, 
	       eg_command( end_command, type )) ;
	 ++warn_count;
         return(0);
	}
    else
    	return(1);
}



