/* Copyright (c) 2002 Hewlett-Packard under GPL version 2 or later */
#include <stdio.h>
#include <stdbool.h>
#include "dmain.h"
#include "pmccabe.h"

/* Global */
int ncss_Line;

/* $Id: dmain.c,v 1.16 2001/01/26 23:00:32 bame Exp $ */

static int Lastc = '\n';

short Pipe[PIPESIZE];
short *Piperead = Pipe;
short *Pipewrite = Pipe;
short *Pipeend = Pipe + PIPESIZE;
bool skipping = false;

FILE *Input;
char Inputfile[1030];

bool Cppflag = false;

enum
{
    PREPROCESSOR_POSITIVE_IF,
    PREPROCESSOR_NEGATIVE_IF,
    PREPROCESSOR_NEVER_IF,
    PREPROCESSOR_ELSE,
    PREPROCESSOR_ENDIF,
    PREPROCESSOR_INCLUDE,
    PREPROCESSOR_DEFINE,
    PREPROCESSOR_UNKNOWN
};

void
skip_within_line(char char_to_skip)
{
    int c;

    while ((c = getc(Input)) != EOF && c != '\n' && c == char_to_skip) ;

    if (c == EOF)
    {
	char error_message[100];

	sprintf(error_message,
		"expected '%c' before end of line, but got EOF",
		char_to_skip);
	fileerror(error_message);
    }

    ungetc(c, Input);
}

void
skip_whitespace_within_line()
{
    skip_within_line(' ');
    skip_within_line('\t');
    skip_within_line(' ');
    skip_within_line('\t');
}



const char *
string_for_preprocessor_token(int preprocessor_token)
{
    switch (preprocessor_token)
    {
    case PREPROCESSOR_DEFINE:
	return "#define";
    case PREPROCESSOR_POSITIVE_IF:
	return "#ifdef";
    case PREPROCESSOR_NEGATIVE_IF:
	return "#ifndef";
    case PREPROCESSOR_NEVER_IF:
	return "#if 0";
    case PREPROCESSOR_ELSE:
	return "#else";
    case PREPROCESSOR_ENDIF:
	return "#endif";
    case PREPROCESSOR_INCLUDE:
	return "#include";
    default:
	return "#UNKNOWN";
    }

}

int
get_preprocessor_token()
{
    skip_whitespace_within_line();

    char first_letter = getc(Input);
    char second_letter;

    switch (first_letter)
    {
    case 'd':
	/* #define */
    case 'u':
	/* #undef */
	return PREPROCESSOR_DEFINE;
    case 'e':
	second_letter = getc(Input);
	if (second_letter == 'l')
	{
	    /* #else or #elif */
	    return PREPROCESSOR_ELSE;
	}
	else if (second_letter == 'n')
	{
	    /* #endif */
	    return PREPROCESSOR_ENDIF;
	}

	return PREPROCESSOR_UNKNOWN;

    case 'i':
	second_letter = getc(Input);
	if (second_letter == 'n')
	{
	    /* #include */
	    return PREPROCESSOR_INCLUDE;
	}

	if (second_letter == 'f')
	{
	    char third_letter = getc(Input);

	    switch (third_letter)
	    {
	    case 'n':
		/* #ifndef */
		return PREPROCESSOR_NEGATIVE_IF;
	    case 'd':
		/* #ifdef */
		return PREPROCESSOR_POSITIVE_IF;
	    case ' ':
	    case '\t':
	    case '(':
		skip_whitespace_within_line();
		skip_within_line('(');
		char logical_identifier = getc(Input);

		switch (logical_identifier)
		{
		case '0':
		    /* #if 0 */
		    return PREPROCESSOR_NEVER_IF;
		case '!':
		    /* #if ! */
		    return PREPROCESSOR_NEGATIVE_IF;
		case 'f':
		    if (getc(Input) == 'a' &&
			getc(Input) == 'l' && getc(Input) == 's')
		    {
			/* #if false */
			return PREPROCESSOR_NEVER_IF;
		    }
		    break;
		case 'd':
		    if (getc(Input) == 'e' &&
			getc(Input) == 'f' &&
			getc(Input) == 'i' && getc(Input) == 'n')
		    {
			/* #if defined() */
			return PREPROCESSOR_POSITIVE_IF;
		    }
		    break;

		default:
		    return PREPROCESSOR_POSITIVE_IF;
		}
	    }
	}
    }

    return PREPROCESSOR_UNKNOWN;
}

static void
better_decomment()
{
    enum
    {
	NORMAL, C_COMMENT, PREPROCESSOR_DIRECTIVE, CC_COMMENT, STRINGLITERAL,
	CHARLITERAL
    } state = NORMAL;

    register int c, c1;

    /* FIXME: decomment() gets called multiple times if a file is longer than the PIPESIZE macro */
    /*                      in that case, this variable being reset mid-file may cause issues. */
    /*                      Making the variable static (alone) isn't a solution, in missing #endif cases */

    int endifs_required = 0;

    Piperead = Pipewrite = Pipe;

    do
    {
	if ((c = getc(Input)) == EOF)
	{
	    if (skipping || endifs_required > 0)
	    {
		fileerror("expected #endif before EOF");
	    }

	    PUTCHAR_REGARDLESS_OF_SKIPPING(EOF);
	    break;
	}

	switch (state)
	{
	case NORMAL:
	    switch (c)
	    {
	    case '#':
		if (!ISSPACE(Lastc))
		{
		    PUTCHAR(c);
		    continue;
		}

		state = PREPROCESSOR_DIRECTIVE;
		if (Cppflag)
		{
		    PUTS("cpp");
		}

		int preprocessor_token = get_preprocessor_token();

		switch (preprocessor_token)
		{
		case PREPROCESSOR_ELSE:
		    /* FIXME: to keep things simple (by avoiding recursive descent),
		     *       the code below means incorrect behavior with:
		     * #if 0
		     * #else
		     * #endif
		     */
		    if (!skipping)
		    {
			endifs_required++;
		    }
		    skipping = true;
		    break;
		case PREPROCESSOR_NEVER_IF:
		    skipping = true;
		    endifs_required++;

		    break;
		case PREPROCESSOR_NEGATIVE_IF:
		case PREPROCESSOR_POSITIVE_IF:
		    if (skipping)
		    {
			endifs_required++;
		    }

		    break;
		case PREPROCESSOR_ENDIF:

		    if (skipping)
		    {
			endifs_required--;
		    }
		}

		if (skipping && endifs_required == 0)
		{
		    skipping = false;
		}

		if (endifs_required < 0)
		{
		    fileerror("expected fewer #endif statements");
		}

		break;
	    case '\'':
		state = CHARLITERAL;
		if (!skipping)
		    PUTS("CHARLITERAL");
		break;
	    case '"':
		state = STRINGLITERAL;
		if (!skipping)
		    PUTS("STRINGLITERAL");
		break;
	    case '/':
		c1 = getc(Input);
		switch (c1)
		{
		case '/':
		    state = CC_COMMENT;
		    break;
		case '*':
		    state = C_COMMENT;
		    break;
		case EOF:
		    /* ending star-slash comment right before EOF is okay */
		    break;
		default:
		    PUTCHAR(c);
		    ungetc(c1, Input);
		    break;
		}
		break;
	    default:
		PUTCHAR(c);
		break;
	    }
	    break;

	case C_COMMENT:	/* K&R C comment */
	    if (c == '\n')
	    {
		PUTCHAR(c);
	    }
	    else if (c == '*')
	    {
		c1 = getc(Input);
		if (c1 == '/')
		{
		    state = NORMAL;
		}
		else
		{
		    ungetc(c1, Input);
		}
	    }
	    break;

	case PREPROCESSOR_DIRECTIVE:
	    switch (c)
	    {
	    case '\n':
		PUTCHAR(c);

		c1 = getc(Input);
		if (c1 != '\\')
		{
		    ungetc(c1, Input);
		    state = NORMAL;
		    break;
		}
		/* FALL THROUGH */
	    case '\\':
		c1 = getc(Input);
		if (c1 == '\n')
		{
		    PUTCHAR(c1);
		    if (Cppflag)
		    {
			PUTS("cpp");
		    }
		}
		break;
	    }
	    break;

	case CC_COMMENT:	/* C++ comment */
	    if (c == '\n')
	    {
		PUTCHAR(c);
		state = NORMAL;
	    }
	    break;

	case STRINGLITERAL:
	    switch (c)
	    {
	    case '\n':
		PUTCHAR(c);
		break;
	    case '"':
		state = NORMAL;
		break;
		/* preserve embedded nulines */
	    case '\\':
		c1 = getc(Input);
		if (c1 == '\n')
		    PUTCHAR(c1);
		break;
	    }
	    break;

	case CHARLITERAL:
	    switch (c)
	    {
	    case '\n':
		PUTCHAR(c);
		break;
	    case '\'':
		state = NORMAL;
		break;
	    case '\\':
		getc(Input);
		break;
	    }
	    break;
	}
	Lastc = c;
    }
    while (state != NORMAL || Pipewrite - Piperead < PIPESIZE / 2);

    return;
}

static void
rudimentary_decomment()
{
    register int c, c1;

    enum
    { NORMAL,
	C_COMMENT,
	CPP,
	CC_COMMENT,
	STRINGLITERAL,
	CHARLITERAL
    } state = NORMAL;

    Piperead = Pipewrite = Pipe;

    do
    {
	if ((c = getc(Input)) == EOF)
	{
	    PUTCHAR(EOF);
	    break;
	}
	if (c == '\r')
	    continue;

	switch (state)
	{
	case NORMAL:
	    switch (c)
	    {
	    case '#':
		if (Lastc == '\n')
		{
		    state = CPP;
		    if (Cppflag)
			PUTS("cpp");
		}
		else
		{
		    PUTCHAR(c);
		}
		break;
	    case '\'':
		state = CHARLITERAL;
		PUTS("CHARLITERAL");
		break;
	    case '"':
		state = STRINGLITERAL;
		PUTS("STRINGLITERAL");
		break;
	    case '/':
		c1 = getc(Input);
		switch (c1)
		{
		case '/':
		    state = CC_COMMENT;
		    break;
		case '*':
		    state = C_COMMENT;
		    break;
		case EOF:
		    break;
		default:
		    PUTCHAR(c);
		    ungetc(c1, Input);
		    break;
		}
		break;
	    default:
		PUTCHAR(c);
		break;
	    }
	    break;

	case C_COMMENT:	/* K&R C comment */
	    if (c == '\n')
	    {
		PUTCHAR(c);
	    }
	    else if (c == '*')
	    {
		c1 = getc(Input);
		if (c1 == '/')
		{
		    state = NORMAL;
		}
		else
		{
		    ungetc(c1, Input);
		}
	    }
	    break;

	case CPP:
	    switch (c)
	    {
	    case '\n':
		PUTCHAR(c);
		state = NORMAL;
		break;
	    case '\\':
		c1 = getc(Input);
		if (c1 == '\n')
		{
		    PUTCHAR(c1);
		    if (Cppflag)
			PUTS("cpp");
		}
		break;
	    }
	    break;

	case CC_COMMENT:	/* C++ comment */
	    if (c == '\n')
	    {
		PUTCHAR(c);
		state = NORMAL;
	    }
	    break;

	case STRINGLITERAL:
	    switch (c)
	    {
	    case '\n':
		PUTCHAR(c);
		break;
	    case '"':
		state = NORMAL;
		break;
		/* preserve embedded nulines */
	    case '\\':
		c1 = getc(Input);
		if (c1 == '\n')
		    PUTCHAR(c1);
		break;
	    }
	    break;

	case CHARLITERAL:
	    switch (c)
	    {
	    case '\n':
		PUTCHAR(c);
		break;
	    case '\'':
		state = NORMAL;
		break;
	    case '\\':
		getc(Input);
		break;
	    }
	    break;
	}
	Lastc = c;
    }
    while (state != NORMAL || Pipewrite - Piperead < PIPESIZE / 2);
}

void
decomment()
{
    extern bool BetterDecomment;

    if (BetterDecomment)
	better_decomment();
    else
	rudimentary_decomment();
}

void
decomment_file(FILE * f)
{
    int c;
    extern bool Cppflag;

    Input = f;

    Cppflag = true;

    while ((c = Getchar()) != EOF)
    {
	putc(c, stdout);
    }
}

int
decomment_files(int argc, char *argv[])
{
    int result = 0;

    if (argc == 1)
    {
	decomment_file(stdin);
    }
    else
    {
	while (argc > 1)
	{
	    FILE *f;

	    if ((f = fopen(argv[1], "r")) == NULL)
	    {
		result = 2;
		perror(argv[1]);
	    }
	    else
	    {
		decomment_file(f);
		fclose(f);
	    }
	    SHIFT(1);
	}
    }

    return result;
}

void
ncss_Ungetc(int c)
{
    if (c == T_NCNULINE)
    {
	ncss_Line--;
    }
    Ungetc(c);
}

void
ncss_Ungets(char *s)
{
    Ungets(s);
}

int
ncss_Getchar()
{
    int c;
    static int blankline = 1;

    if ((c = Getchar()) != EOF)
    {
	if (blankline)
	{
	    if (!ISSPACE(c))
	    {
		blankline = 0;
	    }
	}
	else
	{
	    if (c == '\n')
	    {
		c = T_NCNULINE;
		blankline = 1;
	    }
	}
    }
    else
    {
	blankline = 1;
    }

    if (c == T_NCNULINE)
	ncss_Line++;

    return c;
}

void
ncss(int *linesp, int *nclinesp)
{
    int c;
    int lines = 0;
    int nclines = 0;
    int boline = 1;

    while ((c = Getchar()) != EOF)
    {
	if (c == '\n')
	{
	    lines++;
	    boline = 1;
	}
	else if (boline && !ISSPACE(c))
	{
	    boline = 0;
	    nclines++;
	}
    }

    *linesp = lines;
    *nclinesp = nclines;
}

void
ncss_file(char *fname, FILE * f, int *linesp, int *nclinesp)
{
    extern bool Cppflag;
    int lines, nclines;

    Input = f;

    Cppflag = true;
    ncss(&lines, &nclines);
    if (!Totalsonly)
    {
	if (lines != 0)
	{
	    int pct_csl =
		(int) (0.4999999 + 100.0 * (lines - nclines) / lines);

	    printf("%6d%4d%6d%4d%7d  %-s\n",
		   lines - nclines,
		   pct_csl, nclines, 100 - pct_csl, lines, fname);
	}
	else
	{
	    printf("%6d n/a%6d n/a%7d  %-s\n",
		   lines - nclines, nclines, lines, fname);
	}
    }

    *linesp += lines;
    *nclinesp += nclines;
}

int
ncss_files(int argc, char *argv[])
{
    int result = 0;
    int lines = 0, nclines = 0;
    int nfiles = argc - 1;

    if (Verbose)
	puts("   CSL PCT  NCSL PCT  TOTAL  FILENAME");

    if (argc == 1)
    {
	ncss_file("stdin", stdin, &lines, &nclines);
    }
    else
    {
	while (argc > 1)
	{
	    FILE *f;

	    if ((f = fopen(argv[1], "r")) == NULL)
	    {
		result = 2;
		perror(argv[1]);
	    }
	    else
	    {
		ncss_file(argv[1], f, &lines, &nclines);
		fclose(f);
	    }
	    SHIFT(1);
	}
    }

    if (Totals)
    {
	if (lines != 0)
	{
	    int pct_csl =
		(int) (0.4999999 + 100.0 * (lines - nclines) / lines);

	    printf("%6d%4d%6d%4d%7d  (total files: %d)\n",
		   lines - nclines,
		   pct_csl, nclines, 100 - pct_csl, lines, nfiles);
	}
	else
	{
	    printf("%6d n/a%6d n/a%7d  (total files: %d)\n",
		   lines - nclines, nclines, lines, nfiles);
	}
    }

    return result;
}
