/* C to HTML translator.  Makes spiffy HTML pages out of C code
 *
 * Run it via "c2html myfile.c myfile.c.html".
 *
 * Tom St Denis -- http://tomstdenis.home.dhs.org
 *
 * Gilles Bannay -- http://gilles.bannay.free.fr/
 */
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define VERSION     "1.10"

#define NOT(a)  (!(a))

/* colors */
#define VAR_COL     0xFF0000    /* variables -- red */
#define CMT_COL     0x007F00    /* comments -- green */
#define TXT_COL     0x0000FF    /* text -- blue */
#define PP_COL      0x009F9F    /* pre-processor commands -- light blue */
#define NUM_COL     0xFF00FF    /* numbers -- purple */
#define STM_COL     0x7F0000    /* statements -- brown */

int incol = 0, incom = 0, intext = 0;
int indef = 0, pp = 0;

FILE *out;

int end_color = 0;
int cur_rgb = 0;
int new_rgb = 0;

void emit_color(long rgb)
{
    if (incol) {
        //fprintf(out, "</FONT>");
        end_color = 1; //memo last rgb
        incol = 0;
        if (rgb) incol = 1;
        new_rgb = rgb;
        return;
    }

    if (rgb) {
        incol = 1;
        if (end_color) {
            end_color = 0;
            if (rgb==cur_rgb) return;
            fprintf(out, "</FONT>");
        }
        fprintf(out, "<FONT COLOR=#%06x>", rgb);
        cur_rgb = rgb;
    }
}

void emit_header(char *name)
{
    fprintf(out, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\r\n");
    fprintf(out, "<HTML>\r\n");
    fprintf(out, "\t<HEAD>\r\n");
    fprintf(out, "\t\t<META http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\">\r\n");
    fprintf(out, "\t\t<META name=\"description\" content=\"colored %s source file\">\r\n", name);
    fprintf(out, "\t\t<META name=\"author\" content=\"Gilles Bannay\">\r\n");
    fprintf(out, "\t\t<META name=\"generator\" content=\"C2HTML Version " VERSION "\">\r\n");
    fprintf(out, "\t\t<META name=\"copyright\" content=\"(C) 2007 Gilles Bannay\">\r\n");
    fprintf(out, "\t\t<META name=\"reply-to\" content=\"gilles.bannay@free.fr\">\r\n");
    fprintf(out, "\t\t<TITLE>%s</TITLE>\r\n", name);
    fprintf(out, "\t</HEAD>\r\n");
    fprintf(out, "<BODY BGCOLOR=#FFFFFF>\r\n<PRE>\r\n");
}

void emit_footer(void)
{
    if (end_color) {
        end_color = 0;
        fprintf(out, "</FONT>");
    }
    fprintf(out, "</PRE>\r\n</BODY>\r\n</HTML>\r\n\r\n");
}

void emit_char(char c)
{
    if NOT(isspace(c)) {
        if (end_color) {
            end_color = 0;
            if (cur_rgb!= new_rgb) {
                cur_rgb = new_rgb;
                fprintf(out, "</FONT>");
                if (cur_rgb) {
                    fprintf(out, "<FONT COLOR=#%06x>", cur_rgb);
                }
            }
        }
    }

    switch (c) {
    case '\r':
        break;
    case '\n':
        fputc('\r', out);
        fputc(c, out);
        break;
    case '<':
        fprintf(out, "&lt;");
        break;
    case '>':
        fprintf(out, "&gt;");
        break;
    case '&':
        fprintf(out, "&amp;");
        break;
    default:
        fputc(c, out);
        break;
    }
}

int isdel(char c)
{
    static char *cmp = " ()[]{}+\t:\n+-/*|&^%!~:?";
    int x;
    for (x = 0; cmp[x]; x++)
        if (cmp[x] == c)
            return 1;
    return 0;
}

int is_symbol(char *buf, int x, int z)
{
    int y;
    for (y = x; buf[y] && isalnum(buf[y]) && z--; y++);

    if (!x && isdel(buf[y]))
        return 1;
    if (x)
        if (isdel(buf[x - 1]) && isdel(buf[y]))
            return 1;
    return 0;
}

void do_line(char *in)
{
    static char *stmt[] = {
        "if", "else", "for", "do", "while", "switch", "case", "break",
        "continue", "return", "goto", "sizeof" };

    static char *vtype[] = {
        "signed", "unsigned", "char", "int", "long", "float", "double",
        "struct", "union", "typedef", "const", "extern", "static",
        "volatile", "auto", "short", "void" };


    char buf[4096];
    int len, p, z, y, x, newcmt = 0, ct = 0;
    int x_cur;

    /* expand tabs to 8 spaces */
    memset(buf, 0, sizeof(x));
    for (x_cur = x = y = 0; in[x]; x++) {
        if (in[x] == '\t') {
            do {
                buf[y++] = ' ';
                x_cur++;
            } while ((x_cur % 8) != 0);
        } else {
            buf[y++] = in[x];
            x_cur++;
            if ((in[x] == '\r') || (in[x] == '\n')) {
                buf[y-1] = '\n';
                break;
            }
        }
    }
    len = y;
    buf[y++] = '\0';

    /* go thru the line */
    for (x = 0; x < len; x++) {
        z = y = x;
        if (!incom && !intext && !pp && !indef) {
            /* is it a number? */
            if ((x ? isdel(buf[x - 1]) : 1) && isdigit(buf[x])) {
                emit_color(NUM_COL);
                while (isdigit(buf[x]) || (buf[x] == '.') || (buf[x] == 'x') || ((toupper(buf[x]) >= 'A') && (toupper(buf[x]) <= 'F')))
                    emit_char(buf[x++]);
                emit_color(0);
                emit_char(buf[x]);
                y = x;
            }
            else {
                for (p = 0; p < (sizeof(stmt) / sizeof(char *)); p++)
                    if (!memcmp(buf + x, stmt[p], strlen(stmt[p])))
                        if (is_symbol(buf, x, strlen(stmt[p]))) {
                            x += strlen(stmt[p]) - 1;
                            emit_color(STM_COL);
                            break;
                        }
                if (p == (sizeof(stmt) / sizeof(char *))) {
                    for (p = 0; p < (sizeof(vtype) / sizeof(char *)); p++)
                        if (!memcmp(buf + x, vtype[p], strlen(vtype[p])))
                            if (is_symbol(buf, x, strlen(vtype[p]))) {
                                x += strlen(vtype[p]) - 1;
                                emit_color(VAR_COL);
                                break;
                            }
                }
                if (y != x)
                    while (y <= x)
                        emit_char(buf[y++]);
                y = x;
                emit_color(0);
            }
        }
        /* comment? */
        if (z == y) {
            if ((!pp && !intext && !incom && !indef) && (!memcmp(buf + x, "/*", 2) || !memcmp(buf + x, "//", 2))) {
                emit_color(CMT_COL);
                if (!memcmp(buf + x, "//", 2)) {
                    fprintf(out, "//");
                    newcmt = 1;
                }
                else
                    fprintf(out, "/*");
                x += 1;
                incom = 1;
            }
            else if ((!pp && !intext && !indef) && !memcmp(buf + x, "*/", 2)) {
                x += 1;
                incom = 0;
                fprintf(out, "*/");
                emit_color(0);
            }
            else if (!pp && !intext && !incom && !indef && !memcmp(buf+x, "#define", 7)) {
                emit_color(PP_COL);
                emit_char('#');
//              printf("set defined color\n");
                pp = 1;
                indef = 0;
            }
            else if (pp && !intext && !incom && (buf[x]=='\\')) {
                emit_char(buf[x]);
                if (buf[x+1]=='\n') {
                    emit_char('\n');
                    x++;
//                  printf("continue with defined color, x=%d, len=%d\n",x,len);
                    pp = 1;
                    indef = 1;
                }
            }
            else if (pp && !intext && !incom && (buf[x]=='\n')) {
                    emit_char('\n');
//                  printf("reset defined color\n");
                    pp = 1;
                    indef = 0;
            }
            else if ((!pp && !incom && !indef) && ((buf[x] == '\"') || (buf[x] == '\''))) {
                if (!intext) {
                    ct = buf[x];
                    intext = 1;
                    emit_color(TXT_COL);
                    emit_char(buf[x]);
                }
                else {
                    emit_char(buf[x]);
                    if (buf[x + 1] != ct)
                        if (ct == buf[x]) {
                            intext = 0;
                            emit_color(0);
                        }
                }
            }
            else if (!intext && !incom && !memcmp(buf + x, "#include", 8)) {
                pp = 1;
                emit_color(PP_COL);
                emit_char('#');
            }
            else if (!intext && !incom && !memcmp(buf + x, "#ifdef", 6)) {
                pp = 1;
                emit_color(PP_COL);
                emit_char('#');
            }
            else if (!intext && !incom && !memcmp(buf + x, "#ifndef", 7)) {
                pp = 1;
                emit_color(PP_COL);
                emit_char('#');
            }
            else if (!intext && !incom && !memcmp(buf + x, "#endif", 6)) {
                pp = 1;
                emit_color(PP_COL);
                emit_char('#');
            }
            else if (!intext && !incom && !memcmp(buf + x, "#undef", 6)) {
                pp = 1;
                emit_color(PP_COL);
                emit_char('#');
            }
            else if (!intext && !incom && !memcmp(buf + x, "#elif", 5)) {
                pp = 1;
                emit_color(PP_COL);
                emit_char('#');
            }
            else if (!intext && !incom && !memcmp(buf + x, "#if", 3)) {
                pp = 1;
                emit_color(PP_COL);
                emit_char('#');
            }
            else if (!intext && !incom && !memcmp(buf + x, "#else", 3)) {
                pp = 1;
                emit_color(PP_COL);
                emit_char('#');
            }
            else
                emit_char(buf[x]);
        }
    }

    if (newcmt)
        incom = 0;
    if (pp && !indef) {
        //printf("reset color\n");
        pp = 0;
        emit_color(0);
    }
}

int Help(void)
{
    printf("\nC2HTML infile.c outfile.c.html                        "VERSION);
    printf("\n");
    printf("\n  Permet de convertir un fichier source C en un fichier de");
    printf("\n  type HTML avec les caracteristiques suivantes :");
    printf("\n");
    printf("\n  Tab                     convertis en espaces (de 1 a 8).");
    printf("\n  Retour                  convertis en CR + LF.");
    printf("\n  Commentaires            colorises en vert.");
    printf("\n  Preprocesseur           colorises en cyan.");
    printf("\n  Mots cle de definition  colorises en rouge.");
    printf("\n  Mots cle de traitement  colorises en marron.");
    printf("\n  Constantes caractere    colorises en bleu.");
    printf("\n  Constantes chaine       colorises en bleu.");
    printf("\n  Constantes numerique    colorises en violet.");
    printf("\n");
    printf("\nAuteur:                                    gilles.bannay@free.fr\n");
    return 0;
}

int main(int argc, char **argv)
{
    char buf[4096];
    FILE *in;

    if (argc==1) {
        printf("Version "VERSION" ('c2html -h' pour help)\n");
        return 0;
    }

    if ((argv[1][0]=='-') && ((argv[1][1] | 0x20)=='h')) {
        return Help();
    }

    in = fopen(argv[1], "r");
    if (in == NULL) {
        printf("Can't open '%s' for reading.\n", argv[1]);
        return -1;
    }
    out = fopen(argv[2], "w+b");
    if (out == NULL) {
        printf("Can't open '%s' for writting.\n", argv[2]);
        fclose(in);
        return -1;
    }

    emit_header(argv[1]);
    while (fgets(buf, 4096, in))
        do_line(buf);
    emit_footer();
    return 0;
}