/*-< ANTIC.C >------------------------------------------------------*--------*/
/* Jlint                      Version 1.08       (c) 1998  GARRET   *     ?  */
/* (Java Lint)                                                      *   /\|  */
/*                                                                  *  /  \  */
/*                          Created:     28-Apr-98    K.A. Knizhnik * / [] \ */
/*                          Last update: 18-May-99    E.G. Parmelan * GARRET */
/*------------------------------------------------------------------*--------*/
/* AntiC - C/C++/Java syntax verifier                               *        */
/*------------------------------------------------------------------*--------*/
/* Edouard G. Parmelan e-mail address: edouard.parmelan@quadratec.fr         */
/*------------------------------------------------------------------*--------*/

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <dirent.h>
#endif

#ifndef MAX_PATH
#define MAX_PATH 1024
#endif

char* file_name;
FILE* f;
int line;
int coln;

int force_java;
int token_line;
int token_coln;
int java;
int fall_thru_cmt;
int notreached_cmt;
int tab_size = 8;
int relax_else = 0;

/* Use only lowercase letters without spaces */
const char *fall_thru_cmt_text[] = {
    "fallthr",
    "nobreak"
};

/* Use only lowercase letters without spaces */
const char *notreached_cmt_text[] = {
    "notreach",
    "unreach",
};

#define items(a) (sizeof(a)/sizeof(*(a)))
#define nobreak

enum { 
    TKN_FOR = 257, 
    TKN_IF, 
    TKN_DO, 
    TKN_WHILE, 
    TKN_ELSE,
    TKN_BREAK,
    TKN_CASE,
    TKN_CATCH,
    TKN_FINALLY,
    TKN_TRY,
    TKN_SWITCH,
    TKN_STR,
    TKN_IDENT,
    TKN_CTX,
    TKN_ACCESS,
    TKN_AND_OP,
    TKN_OR_OP,
    TKN_BIN_OP,
    TKN_BIT_OP,
    TKN_SET_OP,
    TKN_CMP_OP,
    TKN_INC_OP,
    TKN_SHIFT_OP,
    TKN_INCLUDE
};

typedef enum {
    ctx_statement,
    ctx_typedef, 
    ctx_body, 
    ctx_switch
} statement_ctx;



int get()
{
    int ch = getc(f);
    if (ch == '\n') { 
	line += 1;
	coln = 0;
    } else if (ch == '\t') { 
	coln += tab_size;
	coln -= coln % tab_size;
    } else { 
	coln += 1;
    }
    return ch;
}

void unget(int ch)
{
    ungetc(ch, f);
    if (ch == '\n') { 
	line -= 1;
    } else if (ch == '\t') { 
	coln -= tab_size;
    } else { 
	coln -= 1;
    }
}

int n_messages;
    
void message_at(int line, int coln, char* msg)
{
    printf("%s:%d:%d: %s\n", file_name, line, coln, msg);
    n_messages += 1;
}

void message(char* msg)
{
    message_at(token_line, token_coln, msg);
}

int check_special_character()
{
    int ch = get();
    switch (ch) { 
      case '\n':
	if (java) { 
	    message("Newline within string\n");
	}
	break;
      case 'n':
      case 't':
      case 'b':
      case 'r':
      case 'f':
      case '\\':
      case '"':
      case '\'':
	break;
      case '0': case '1': case '2': case '3':
      case '4': case '5': case '6': case '7':
	ch = get();
	if (ch >= '0' && ch <= '9') {
	    if (ch > '7') { 
		message("Octal digit expected");
	    }
	    ch = get();
	    if (ch >= '0' && ch <= '9') {
		if (ch > '7') { 
		    message("Octal digit expected");
		}
		ch = get();
		if (ch >= '0' && ch <= '9') {
		    message("May be more than three octal digits are specified");
		}
	    }
	}
	return ch;
      case 'x':
	if (java) {
	    message("Hexadecimal constant\n");
	}
	ch = get();
	if (!isxdigit(ch)) {
	    message("Hexadecimal digit expected\n");
	}
	else while (isxdigit(ch)) {
	    ch = get();
	}
	return ch;
      case 'u':
	if (java) {
	    do { 
		ch = get();
	    } while (ch == 'u');
	    if (!isxdigit(ch)) { 
		message("Invalid character constant");
	    }
	    ch = get();
	    if (!isxdigit(ch)) { 
		message("Invalid character constant");
	    }
	    ch = get();
	    if (!isxdigit(ch)) { 
		message("Invalid character constant");
	    }
	    ch = get();
	    if (!isxdigit(ch)) { 
		message("Invalid character constant");
	    }
	    ch = get();
	    if (isxdigit(ch)) { 
		message("May be more than four hex digits "
			"are specified for character constant");
	    }	
	    return ch;
	}	
	nobreak;
      default:
	message("May be incorrect escape sequence");
    }
    return get();
}

static void check_fall_thru(int ch)
{
    static unsigned index[items(fall_thru_cmt_text)];
    unsigned i;

    if (ch == ' ' || ch == '-') { 
	return;
    }
    if (ch == 0) {
	fall_thru_cmt = 0;
	for (i = 0; i < items(fall_thru_cmt_text); i++)
	    index[i] = 0;
    }
    else if (!fall_thru_cmt) {
	for (i = 0; i < items(index); i++) {
	    if (tolower(ch) == fall_thru_cmt_text[i][index[i]]) {
		if (fall_thru_cmt_text[i][++(index[i])] == '\0') {
		    fall_thru_cmt = 1;
		    return;
		}
	    }
	    else {
		index[i] = 0;
	    }
	}
    }
}

static void check_notreached(int ch)
{
    static unsigned index[items(notreached_cmt_text)];
    unsigned i;

    if (ch == ' ' || ch == '-') { 
	return;
    }
    if (ch == 0) {
	notreached_cmt = 0;
	for (i = 0; i < items(notreached_cmt_text); i++)
	    index[i] = 0;
    }
    else if (!notreached_cmt) {
	for (i = 0; i < items(index); i++) {
	    if (tolower(ch) == notreached_cmt_text[i][index[i]]) {
		if (notreached_cmt_text[i][++(index[i])] == '\0') {
		    notreached_cmt = 1;
		    return;
		}
	    }
	    else {
		index[i] = 0;
	    }
	}
    }
}

int scan_0(int cpp)
{
    int ch;
    unsigned i;
    char buf[256];
    
  skip_spaces:
    do {
	ch = get();
	if ((ch == EOF) || (ch == '\n') || (ch == '#'))
	    return ch;
    } while (isspace(ch));
    token_coln = coln;
    token_line = line;
    switch (ch) {
      case '/':
	ch = get();
	if (ch == '=') { 
	    return TKN_SET_OP;
	} else if (ch == '*') { 
	    int nested = 0;
	    ch = get();
	    while (1) { 
		if (ch == EOF) { 
		    message("Unclosed comments");
		    return EOF;
		} else if (ch == '/') { 
		    ch = get();
		    if (ch == '*') { 
			if (!nested)
			    message("Nested comments");
			nested = 1;
		    }
		} else if (ch == '*') { 
		    ch = get();
		    if (ch == '/') { 
			goto skip_spaces;
		    }
		} else { 
		    check_fall_thru(ch);
		    check_notreached(ch);
		    ch = get();
		}
	    }		
	} else if (ch == '/') {
	    while ((ch = get()) != '\n' && ch != EOF) { 
		check_fall_thru(ch);
		check_notreached(ch);
	    }
	    return ch;
	}
	unget(ch);
	return TKN_BIN_OP;	
      case '"':
	ch = get();
	while (ch != '"') {
	    switch (ch) { 
	      case '\n':
		message("Newline within string constant");
		ch = get();
		continue;
	      case EOF: 
		return EOF;
	      case '\\':
		if (cpp == 2) {
		    ch = get();
		}   
		else {
		    ch = check_special_character();
		}
		continue;
	      case '?':
		if (!java) { 
		    ch = get();
		    if (ch == '?') { 
			ch = get();
			if (ch == '=' || ch == '/' || ch == '\'' ||
			    ch == '(' || ch == ')' || ch == '!' || 
			    ch == '<' || ch == '>') 
			{
			    message("Trigraph sequence inside string");
			}
		    }
		    continue;
		}
		nobreak;
	      default:
		ch = get();
	    }
	}
	return TKN_STR;
      case '\'':
	ch = get();
	if (ch == '\\') { 
	    ch = check_special_character();
	} else { 
	    ch = get();
	}
	if (ch != '\'') { 
	    message("Multibyte character constants are not portable");
	    do {
		ch = get();
		if (ch == '\\')
		    get();
	    } while ((ch != '\'') && (ch != EOF));
	}
	return TKN_STR;
      case ':':
	if (!java) { 
	    ch = get();
	    if (ch == ':') {
		return TKN_CTX;
	    } else { 
		unget(ch);
		return ':';
	    }
	}
	break;
      case '+':
	ch = get();
	if (ch == '=') { 
	    return TKN_SET_OP;
	} else if (ch == '+') return TKN_INC_OP;
	unget(ch);
	return '+';	
      case '-':
	ch = get();
	if (ch == '=') { 
	    return TKN_SET_OP;
	} else if (!java && ch == '>') { 
	    return TKN_ACCESS;
	} else if (ch == '-') return TKN_INC_OP;
	unget(ch);
	return '-';	
      case '*':
	ch = get();
	if (ch == '=') { 
	    return TKN_SET_OP;
	}
	unget(ch);
	return '*';	
      case '%':
	ch = get();
	if (ch == '=') { 
	    return TKN_SET_OP;
	}
	unget(ch);
	return TKN_BIN_OP;	
      case '&':
	ch = get();
	if (ch == '=') { 
	    return TKN_SET_OP;
	} else if (ch == '&') { 
	    return TKN_AND_OP;
	}
	unget(ch);
	return '&';
      case '|':
	ch = get();
	if (ch == '=') { 
	    return TKN_SET_OP;
	} else if (ch == '|') { 
	    return TKN_OR_OP;
	}
	unget(ch);
	return TKN_BIT_OP;
      case '^':
	ch = get();
	if (ch == '=') { 
	    return TKN_SET_OP;
	}
	unget(ch);
	return TKN_BIT_OP;
      case '>':
	ch = get();
	if (ch == '>') { 
	    ch = get();
	    if (java && ch == '>') { 
		ch = get();
		if (ch == '=') return TKN_SET_OP;
	    } else if (ch == '=') return TKN_SET_OP;
	    unget(ch); 
	    return TKN_SHIFT_OP;
	} else if (ch != '=') unget(ch); 
	return TKN_CMP_OP;
      case '<':
	ch = get();
	if (ch == '<') { 
	    ch = get();
	    if (ch == '=') return TKN_SET_OP;
	    unget(ch); 
	    return TKN_SHIFT_OP;
	} else if (ch != '=') unget(ch); 
	return TKN_CMP_OP;	
      case '=':
	ch = get();
	if (ch == '=') return TKN_CMP_OP;
	unget(ch); 
	return '=';	
      case '!':
	ch = get();
	if (ch == '=') return TKN_CMP_OP;
	unget(ch); 
	return '!';	
      default:
	if (isalnum(ch) || ch == '_' || ch == '$') { 
	    i = 0;
	    do { 
		if (i < sizeof(buf)) { 
		    buf[i++] = ch;
		}
		ch = get();
	    } while (isalnum(ch) || ch == '_' || ch == '$');
	    unget(ch);
	    if (i < sizeof(buf)) { 
		buf[i] = '\0';
		if (strcmp(buf, "if") == 0)       return TKN_IF; 
		if (strcmp(buf, "for") == 0)      return TKN_FOR; 
		if (strcmp(buf, "while") == 0)    return TKN_WHILE; 
		if (strcmp(buf, "do") == 0)       return TKN_DO; 
		if (strcmp(buf, "else") == 0)     return TKN_ELSE;
		if (strcmp(buf, "default") == 0)  return TKN_CASE;
		if (strcmp(buf, "case") == 0)     return TKN_CASE;
		if (strcmp(buf, "break") == 0)    return TKN_BREAK;
		if (strcmp(buf, "goto") == 0)     return TKN_BREAK;
		if (strcmp(buf, "throw") == 0)    return TKN_BREAK;
		if (strcmp(buf, "return") == 0)   return TKN_BREAK;
		if (strcmp(buf, "continue") == 0) return TKN_BREAK;
		if (strcmp(buf, "catch") == 0)    return TKN_CATCH;
		if (strcmp(buf, "try") == 0)      return TKN_TRY;
		if (strcmp(buf, "finally") == 0)  return TKN_FINALLY;
		if (strcmp(buf, "switch") == 0)   return TKN_SWITCH;
		if (!java && strcmp(buf, "nobreak") == 0)  return TKN_BREAK;
		if ((cpp == 1) && strcmp(buf, "include") == 0) return TKN_INCLUDE;
	    }
	    if (isdigit((unsigned char)buf[0]) && buf[i-1] == 'l') { 
		message("May be 'l' is used instead of '1' at the end of "
			"integer constant");
	    } 
	    return TKN_IDENT;
	}
    }
    return ch;
}


int scan()
{
    int ch;

    check_fall_thru(0);
    check_notreached(0);

    while (1) {
	ch = scan_0(0);
	if (ch == '#') {
	    int cpp = 1;
	    do {
		ch = scan_0(cpp);
		if (cpp == 1 && ch == TKN_INCLUDE)
		    cpp = 2;
		if (ch == '\\')
		    scan_0(cpp);
	    } while ((ch != '\n') && (ch != EOF));
	}
	else if (ch == '\\') {
	    message("Unexpected character '\\'");
	}
	else if (ch != '\n')
	    return ch;
    }
}


int parse_binary_operation(int* n_bin_ops);

int parse_expression()
{
    int lex, prev_op = 0;
    while (1) { 
	int n_bin_ops;
	lex = parse_binary_operation(&n_bin_ops);
	if (lex == '&') lex = TKN_BIT_OP;
	if (lex == TKN_CMP_OP || lex == TKN_BIT_OP || lex == TKN_AND_OP
	    || lex == TKN_OR_OP || lex == TKN_SET_OP || lex == '=' 
	    || lex == ',' || lex == '?' || lex == ':') 
        {
	    if (!java && lex == TKN_CMP_OP && prev_op == TKN_CMP_OP) { 
		prev_op = 0; /* may be template */
		continue;
	    }
	    if ((lex == TKN_CMP_OP && prev_op == TKN_BIT_OP)
		|| (lex == TKN_BIT_OP 
		    && (n_bin_ops != 0 || prev_op == TKN_AND_OP 
			|| prev_op == TKN_OR_OP || prev_op == TKN_CMP_OP))
		|| ((lex == TKN_AND_OP || lex == TKN_OR_OP) 
		    && (prev_op == TKN_BIT_OP || prev_op == '=' ||
			prev_op == TKN_SET_OP))
		|| ((lex == '=' || lex == TKN_SET_OP) 
		    && (prev_op == TKN_AND_OP || prev_op == TKN_OR_OP)))
	    {
		message("May be wrong assumption about operators priorities");
	    }
	    else if (lex == TKN_AND_OP && prev_op == TKN_OR_OP) {  
		message("May be wrong assumption about logical "
			"operators precedence");	    
	    } 
	    prev_op = lex;
	} else return lex;
    }
}

void parse_block(int* pass_through, statement_ctx ctx);

int parse_term()
{
    int lex;
    
    while ((lex = scan()) == '&' || lex == '+' || lex == '-' || lex == '*' 
	   || lex == '!' || lex == TKN_INC_OP || lex == TKN_CTX || lex == '~');
    while (lex == '.' || lex == TKN_INC_OP || lex == TKN_IDENT 
	   || lex == TKN_ACCESS || lex == '(' || lex == '[' || lex == TKN_STR 
	   || lex == TKN_CTX)
    {
	if (lex == '(') { 
	    int lpr_line = token_line;
	    int lpr_coln = token_coln;
	    lex = parse_expression();
	    if (lex != ')') { 
		message_at(lpr_line, lpr_coln, "Unbalanced '('");
		return lex;
	    }
	} else if (lex == '[') { 
	    int lbr_line = token_line;
	    int lbr_coln = token_coln;
	    lex = parse_expression();
	    if (lex != ']') { 
		message_at(lbr_line, lbr_coln, "Unbalanced '['");
		return lex;
	    }
	} 
	lex = scan();
    }
    return lex;
}

int parse_switch_expression()
{
    int lex = scan();
    int lpr_line = token_line;
    int lpr_coln = token_coln;
    if (lex != '(') { 
	message_at(lpr_line, lpr_coln, "'(' expected");
	return lex;
    }
    lex = parse_expression();
    if (lex != ')') { 
	message_at(lpr_line, lpr_coln, "Unbalanced '('");
    }
    return scan();
}

int parse_binary_operation(int* n_bin_ops)
{
    int lex;
    int prev_op = 0;
    *n_bin_ops = 0;
    while (1) { 
	lex = parse_term();
	if (lex == '*' || lex == '-' || lex == '+') lex = TKN_BIN_OP;
	if (lex == TKN_SHIFT_OP) { 
	    if (prev_op == TKN_BIN_OP) { 
 		message("May be wrong assumption about shift operator priority");
	    } 
	} else if (lex == TKN_BIN_OP) { 
	    if (prev_op == TKN_SHIFT_OP) {
		message("May be wrong assumption about shift operator priority");
	    }
	} else if (java && lex == '{') { /* anonymous class */
	    int pass_through = 1;
	    parse_block(&pass_through, ctx_typedef);
	} else { 
	    return lex;
	}
	prev_op = lex;
	*n_bin_ops += 1;
    }
}


void parse_conditional_expression(int term_ch)
{
    int bit_op_line = 0;
    int bit_op_coln = 0;
    int n_bin_ops;
    int lex;
    int expr_line = 0;
    int expr_coln = 0;
    int prev_op = 0;

    if (term_ch == ')') { 
	if (scan() != '(') { 
	    message("'(' expected");
	    return;
	}
	expr_line = token_line;
	expr_coln = token_coln;
    }
    while ((lex = parse_binary_operation(&n_bin_ops)) != term_ch) {
	if (lex == '&') lex = TKN_BIT_OP;
	if (lex == EOF) { 
	    message_at(expr_line, expr_coln, term_ch == ')' 
		       ? "Unbalanced parentheses" 
		       : "No ')' after FOR condition");
	    return;
	} 	    
	if (lex == '=') { 
	    message("May be '=' used instead of '=='");
	} else if (lex == TKN_SET_OP) { 
	    message("May be skipped parentheses around assign operator");
	} else if (lex == TKN_BIT_OP) { 
	    if (n_bin_ops != 0 || prev_op == TKN_AND_OP 
		|| prev_op == TKN_OR_OP || prev_op == TKN_CMP_OP) 
            { 
		message("May be wrong assumption about bit operation priority");
	    } 
	    bit_op_line = token_line;
	    bit_op_coln = token_coln;
	    prev_op = TKN_BIT_OP;
	    continue;
	} else if (lex == TKN_AND_OP && prev_op == TKN_OR_OP) {  
	    message("May be wrong assumption about logical operators precedence");	    
	} else if (lex != '?' && lex != ':' && lex != ',' && lex != TKN_CMP_OP 
		 && lex != TKN_AND_OP && lex != TKN_OR_OP) 
	{
	    message("Syntax error");
	} 
	if (prev_op == TKN_BIT_OP) { 
	    message_at(bit_op_line, bit_op_coln, 
		       "May be wrong assumption about operators priorities");
	}
	prev_op = lex;
    }
}

int parse_statement(int lex, int* stmt_line, int* stmt_coln, 
		    int* pass_through, int* entry_point, statement_ctx ctx) 
{
    int next_stmt_line, next_stmt_coln;
    int if_coln;
    int stmt;
    int if_pass_through, else_pass_through;
    int statement_with_label = 0;
    int n_labels_before;
    int entries;
    static int n_labels;

  classify_statement:
    switch (lex) {
      case '{':
	parse_block(pass_through, ctx);
	nobreak;
      case ';':
	lex = scan();	
	*stmt_line = token_line;
	*stmt_coln = token_coln;
	return lex;
      case TKN_FOR:
	if ((lex = scan()) != '(') { 
	    message("No '(' after FOR");
	    return lex;
	}
	lex = parse_expression();
	if (lex != ';') { 
	    message("No ';' after FOR initialization part");
	    return lex;
	}
	parse_conditional_expression(';');
	lex = parse_expression();
	if (lex != ')') { 
	    message("No ')' after FOR increment part");
	    return lex;
	}
	goto loop_body;
      case TKN_WHILE:
	parse_conditional_expression(')');
      loop_body:
	stmt = scan();
	next_stmt_line = token_line;
	next_stmt_coln = token_coln;
	if_pass_through = 0;
	n_labels_before = n_labels;
	lex = parse_statement(stmt, &next_stmt_line, &next_stmt_coln, 
			      &if_pass_through, &entries, ctx_statement);
	if (if_pass_through) { 
	    *pass_through = 1;
	}
	if (!java && n_labels != n_labels_before) { 
	    *entry_point = 1;
	}
	if (stmt != '{' && lex != '}' && next_stmt_coln > *stmt_coln) { 
	    message_at(next_stmt_line, next_stmt_coln, 
		       "May be wrong assumption about loop body");
	}
	break;
      case TKN_SWITCH:
	stmt = parse_switch_expression();
	next_stmt_line = token_line;
	next_stmt_coln = token_coln;
	if (stmt != '{') { 
	    message_at(next_stmt_line, next_stmt_coln, 
		       "Suspicios SWITCH without body");
	}
	if_pass_through = 0;
	lex = parse_statement(stmt, &next_stmt_line, &next_stmt_coln, 
			      &if_pass_through, &entries, ctx_switch);
	*pass_through = 1;
	break;
      case TKN_IF: 
	if_coln = *stmt_coln;
	parse_conditional_expression(')');
	stmt = scan();
	next_stmt_line = token_line;
	next_stmt_coln = token_coln;
	else_pass_through = if_pass_through = *pass_through;
	lex = parse_statement(stmt, &next_stmt_line, &next_stmt_coln, 
			      &if_pass_through, &entries, ctx_statement);
	if (if_pass_through) { 
	    *pass_through = 1;
	}
	if (lex == TKN_ELSE) { 
	    lex = scan();
	    if (next_stmt_coln < if_coln) {
		/* handle IF ... ELSE FOO where FOO is aligned with IF */
		if (!relax_else || (token_coln < if_coln)) {
		    message_at(next_stmt_line, next_stmt_coln, 
			    "May be wrong assumption about ELSE branch "
			    "association");
		}
	    }
	    next_stmt_coln = token_coln;
	    if (lex == TKN_IF && next_stmt_line == token_line) {  
		if (if_coln < token_coln) { 
		    next_stmt_coln = if_coln;
		}
	    }
	    next_stmt_line = token_line;
	    lex = parse_statement(lex, &next_stmt_line, &next_stmt_coln,
				  &else_pass_through, &entries, ctx_statement);
	    if (!if_pass_through && !else_pass_through) { 
		*pass_through = 0;
	    } else if (else_pass_through) { 
		*pass_through = 1;
	    }
	} 
	else if (stmt == ';' 
		 || (stmt != '{' && lex != '}' && next_stmt_coln > if_coln))
	{ 
	    message_at(next_stmt_line, next_stmt_coln, 
		       "May be wrong assumption about IF body");
	}
	break;
      case TKN_TRY: 
	lex = scan();
	next_stmt_line = token_line;
	next_stmt_coln = token_coln;
	if_pass_through = *pass_through;
	else_pass_through = 0;
	lex = parse_statement(lex, &next_stmt_line, &next_stmt_coln,
			      &if_pass_through, &entries, ctx_statement);
	else_pass_through |= if_pass_through;
	while ((lex == TKN_CATCH) || (lex == TKN_FINALLY)) { 
	    /* It is really not condition expression, but list of 
	     * catch paramters, but is possible to use this function to skip 
	     * catch paramter list.
	     */
	    if (lex == TKN_CATCH)
		parse_conditional_expression(')');
	    if_pass_through = *pass_through;
	    lex = parse_statement(scan(), &next_stmt_line, &next_stmt_coln, 
				  &if_pass_through, &entries, ctx_statement);
	    else_pass_through |= if_pass_through;
	} 
	*pass_through = else_pass_through;
	break;
      case TKN_DO:
	lex = scan();
	next_stmt_line = token_line;
	next_stmt_coln = token_coln;
	n_labels_before = n_labels;
	if_pass_through = 0;
	lex = parse_statement(lex, &next_stmt_line, &next_stmt_coln,
			      &if_pass_through, &entries, ctx_statement);
	if (if_pass_through) { 
	    *pass_through = 1;
	}
	if (!java && n_labels != n_labels_before) { 
	    *entry_point = 1;
	}
	if (lex != TKN_WHILE) { 	
	    message_at(next_stmt_line, next_stmt_coln, 
		       "No WHILE for DO");
	}
	parse_conditional_expression(')');
	lex = scan();
	if (lex != ';') { 
	    message_at(token_line, token_coln, "No ';' after DO WHILE");
	}
	lex = scan();	
	*stmt_line = token_line;
	*stmt_coln = token_coln;
	return lex;
      case TKN_CASE:
	if (ctx != ctx_switch) { 
	    message_at(*stmt_line, *stmt_coln, "Suspicious CASE/DEFAULT");
	} 
	else if (!statement_with_label && !fall_thru_cmt && *pass_through) { 
	    message_at(*stmt_line, *stmt_coln, 
		       "Possible miss of BREAK before CASE/DEFAULT");
	}	    
	while ((lex = scan()) != ':') { 
	    if (lex == EOF) { 
		message_at(*stmt_line, *stmt_coln, "No ':' after CASE");
		return EOF;
	    }
	}
	statement_with_label = 1;
	*entry_point = 1;
	*pass_through = 1;
	lex = scan();
	*stmt_line = token_line;
	*stmt_coln = token_coln;
	goto classify_statement;
      case TKN_BREAK:
	*pass_through = 0;
	goto parse_statement;
      case TKN_CATCH:
      case TKN_FINALLY:
	message_at(*stmt_line, *stmt_coln, "Suspicious CATCH/FINALLY");
	if (lex == TKN_CATCH)
	    parse_conditional_expression(')');
	*pass_through = 1;
	lex = scan();
	*stmt_line = token_line;
	*stmt_coln = token_coln;
	goto classify_statement;
      case TKN_IDENT:
	lex = scan();
	if (lex == ':') { 
	    n_labels += 1;
	    statement_with_label = 1;
	    *entry_point = 1;
	    *pass_through = 1;
	    lex = scan();
	    *stmt_line = token_line;
	    *stmt_coln = token_coln;
	    goto classify_statement;
	}
	nobreak;
      parse_statement:
      default:
	ctx = ctx_typedef;
	while (lex != ';') { 
	    switch (lex) {
	      case EOF:
		message_at(*stmt_line, *stmt_coln, 
			   "Statement not terminated by ';'");
		return EOF;
	      case '(':
		lex = parse_expression();
		if (lex != ')') { 
		    message("')' expected");
		    return lex;
		}
		ctx = ctx_body;
		break;
	      case '[':
		lex = parse_expression();
		if (lex != ']') { 
		    message("']' expected");
		    return lex;
		}
		break;
	      case ')':
		message("Unbalanced ')' in statement");
		lex = scan();
		*stmt_line = token_line;
		*stmt_coln = token_coln;
		goto classify_statement;
	      case TKN_SET_OP:
	      case '=':
		lex = parse_expression();
		continue;
	      case '}':
		*stmt_line = token_line;
		*stmt_coln = token_coln;
		return lex;		
	      case '{':
		if_pass_through = 1;
		parse_block(&if_pass_through, ctx);
		lex = scan();
		*stmt_line = token_line;
		*stmt_coln = token_coln;
		return lex;
	      case TKN_FOR: 
	      case TKN_IF: 
	      case TKN_DO: 
	      case TKN_WHILE: 
	      case TKN_CASE:
	      case TKN_TRY:
	      case TKN_SWITCH:
		message_at(*stmt_line, *stmt_coln, "Possible miss of ';'");
		*stmt_line = token_line;
		*stmt_coln = token_coln;
		goto classify_statement;
	    }
	    lex = scan();	
	}
	lex = scan();	
	*stmt_line = token_line;
	*stmt_coln = token_coln;
	return lex;
    }
    *stmt_line = next_stmt_line;
    *stmt_coln = next_stmt_coln;
    return lex;
}


void parse_block(int* pass_through, statement_ctx ctx)
{
    int block_line = token_line;
    int block_coln = token_coln;
    int lex = scan();
    int stmt_line = token_line;
    int stmt_coln = token_coln;
    int reachable = 1;
    int unreachable = 0;
    int unreachable_line;
    int unreachable_coln;
    int entry_point;

    while (1) { 
	switch (lex) { 
	  case EOF:
	    message_at(block_line, block_coln, "Unbalanced brackets");
	    return;
	  case '}':
	    return;
	  case TKN_ELSE:
	    message_at(stmt_line, stmt_coln, "ELSE without IF");
	    lex = scan();
	    continue;
	  case TKN_FOR: 
	  case TKN_IF: 
	  case TKN_DO: 
	  case TKN_WHILE: 
	  case TKN_BREAK:
	  case TKN_CASE:
	  case TKN_CATCH:
	  case TKN_FINALLY:
	  case TKN_TRY:
	  case TKN_SWITCH:
	    if (ctx == ctx_typedef)
		ctx = ctx_statement;
	    nobreak;
	  default:
	    if (lex == TKN_CASE)
		reachable = 1;
	    else
		reachable = *pass_through;
	    unreachable_line = stmt_line;
	    unreachable_coln = stmt_coln;
	    entry_point = 0;
	    lex = parse_statement(lex, &stmt_line, &stmt_coln, 
				  pass_through, &entry_point, ctx);
	    if (ctx != ctx_typedef && unreachable && !entry_point) { 
		message_at(unreachable_line, unreachable_coln, 
			   "Unreachable statement");
	    } 		
	    if (reachable && !*pass_through && !notreached_cmt) { 
		unreachable = 1;
	    } else { 
		unreachable = 0;
	    }
	}
    }
}

void parse_file()
{
    int lex = scan();

    while (lex != EOF) { 
	if (lex == '{') { 
	    int pass_through = 1;
	    parse_block(&pass_through, ctx_typedef);
	} else if (lex == '=') { 
	    lex = parse_expression();	    
	    continue;
	} 
	lex = scan();
    }
}

int has_suffix(char* str, char* suffix) { 
    int suffix_len = strlen(suffix);
    int str_len = strlen(str);
    if (suffix_len > str_len) { 
	return 0;
    }
    str += str_len - suffix_len;
    while (--suffix_len >= 0) { 
	if (tolower(*(unsigned char*)str) != *suffix) { 
	    return 0;
	}
	suffix += 1;
	str += 1;
    }
    return 1;
}

void load_file(char* name, int recursive) 
{ 
#ifdef _WIN32
    HANDLE dir;
    char dir_path[MAX_PATH];
    WIN32_FIND_DATA file_data;
    if (recursive != 0) { 
	sprintf(dir_path, "%s\\*", name);
	if ((dir=FindFirstFile(dir_path, &file_data)) != INVALID_HANDLE_VALUE) 
	{
	    name = dir_path; 
	}
    } else {
	if (strcmp(name, "..") == 0 || strcmp(name, ".") == 0) { 
	    load_file(name, 1);
	    return;
	}
	if ((dir = FindFirstFile(name, &file_data)) == INVALID_HANDLE_VALUE) { 
	    fprintf(stderr, "Failed to locate file '%s'\n", name);
	    return;
	}
    }
    if (dir != INVALID_HANDLE_VALUE) {
	do {
	    if (!recursive || *file_data.cFileName != '.') { 
		char file_path[MAX_PATH];
		char* file_dir = strrchr(name, '\\');
		char* file_name_with_path;
		if (file_dir != NULL) { 
		    int dir_len = file_dir - name + 1;
		    memcpy(file_path, name, dir_len);
		    strcpy(file_path+dir_len, file_data.cFileName);
		    file_name_with_path = file_path;
		} else { 
		    file_name_with_path = file_data.cFileName;
		}
		load_file(file_name_with_path, recursive+1);
	    }
	} while (FindNextFile(dir, &file_data));
	CloseHandle(dir);
	return;
    }
#else
    DIR* dir = opendir(name);
    if (dir != NULL) { 
	struct dirent* entry;
	while ((entry = readdir(dir)) != NULL) { 
	    if (*entry->d_name != '.') { 
		char full_name[MAX_PATH];
		sprintf(full_name, "%s/%s", name, entry->d_name);
		load_file(full_name, 2);
	    }
	} 
	closedir(dir);
	return;
    }
#endif
    if (recursive < 2
	|| has_suffix(name, ".java") 
	|| has_suffix(name, ".c")
	|| has_suffix(name, ".cpp") 
	|| has_suffix(name, ".cxx")
	|| has_suffix(name, ".cc")
	|| has_suffix(name, ".h")
	|| has_suffix(name, ".hpp"))
    {
	if ((f = fopen(name, "r")) != NULL) { 
	    file_name = name;
	    line = 1;
	    coln = 0;
	    java = force_java || has_suffix(name, ".java");
	    parse_file();
	    fclose(f);
	} else { 
	    fprintf(stderr, "Failed to open file '%s'\n", name);
	}
    }
}

int main(int argc, char* argv[])
{
    int i;
    if (argc == 1) { 
	fprintf(stderr, "Usage: antic [-java] [-tab <TAB SIZE>] [-relax-else] file|dir {file|dir...}\n");
	return 0;
    } 
    for (i = 1; i < argc; i++) { 
	if (*argv[i] == '-') { 
	    if (strcmp(argv[i], "-java") == 0) { 
		force_java = 1;
	    } else if (strcmp(argv[i], "-tab") == 0 && i+1 < argc) { 
		if (sscanf(argv[++i], "%d", &tab_size) != 1) { 
		    tab_size = 8;
		    fprintf(stderr, "Bad value '%s' for TAB size parameter\n",
			    argv[i]);
		}
	    } else if (strcmp(argv[i], "-relax-else") == 0) {
		relax_else = 1;
	    } else { 
		fprintf(stderr, "Unregognized option %s\n"
			"Usage: antic [-java] [-tab <TAB SIZE>] [-relax-else] file|dir {file|dir...}\n", argv[i]);
	    } 
	} else { 
	    load_file(argv[i], 0);
	}
    }
    printf("Verification completed: %d reported messages\n", n_messages);
    return 0;
}	
