From cd8f9fd6a79242c1b2204d0e2b9541ab5d649679 Mon Sep 17 00:00:00 2001 From: cozis Date: Thu, 5 May 2022 14:35:47 +0200 Subject: [PATCH] rewrite --- .gitignore | 2 +- README.md | 2 + build.sh | 2 +- c2html.c | 615 +++++++++++++++++++++++++++++++++++++---------------- c2html.h | 6 +- cli.c | 76 +++++-- style.css | 80 +++++++ 7 files changed, 578 insertions(+), 205 deletions(-) create mode 100644 README.md create mode 100644 style.css diff --git a/.gitignore b/.gitignore index 165c873..aff0596 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -c2html +c2h \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ae5078c --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# c2html +A tool to add HTML syntax highlighting to C code. \ No newline at end of file diff --git a/build.sh b/build.sh index 1c2a105..49beb58 100755 --- a/build.sh +++ b/build.sh @@ -1 +1 @@ -gcc cli.c c2html.c -o c2html -Wall -Wextra \ No newline at end of file +gcc cli.c c2html.c -o c2h -Wall -Wextra -g \ No newline at end of file diff --git a/c2html.c b/c2html.c index e44107d..4c18a64 100644 --- a/c2html.c +++ b/c2html.c @@ -1,17 +1,48 @@ +#include +#include #include #include +#include +#include #include -#include "c2html.h" -/* Converts files containing C code to an - * HTML version with syntax highlighting. - * The highlighting is best-effort, it's - * only based on the tokenization result - * and doesn't do a full parsing. Parsing - * C would blow up the code of this utility. - */ +typedef enum { + T_DONE = 256, + T_COMMENT, + T_SPACE, + T_TAB, + T_NEWL, + T_VSTR, + T_VCHAR, + T_VINT, + T_VFLT, + T_KWORD, + T_FDECLNAME, + T_FCALLNAME, + T_IDENTIFIER, + T_OPERATOR, +} Kind; -static _Bool iskeyword(const char *str) +typedef struct { + Kind kind; int off, len; +} Token; + +static bool isoperat(char c) +{ + return c == '+' + || c == '-' + || c == '*' + || c == '/' + || c == '%' + || c == '=' + || c == '!' + || c == '<' + || c == '>' + || c == '|' + || c == '&'; +} + +static bool iskword(const char *str, long len) { static const char *keywords[] = { "auto", "break", "case", "char", @@ -27,210 +58,422 @@ static _Bool iskeyword(const char *str) }; const int num_keywords = sizeof(keywords)/sizeof(keywords[0]); for(int i = 0; i < num_keywords; i += 1) - if(!strcmp(keywords[i], str)) + if(strlen(keywords[i]) == len && !strncmp(keywords[i], str, len)) return 1; return 0; } -static _Bool isop(char c) +static Token *tokenize(const char *str, long len) { - static const char ops[] = ".<>=!+-*/%&^|;~?:"; - for(int i = 0; ops[i] != '\0'; i += 1) - if(c == ops[i]) - return 1; - return 0; -} + Token *array = NULL; + int count = 0, capacity = 0; -const char *c2html2(FILE *src, FILE *dst) -{ - char c = getc(src); - - while(isspace(c)) { - if(c == ' ') - fprintf(dst, " "); - else if(c == '\n') - fprintf(dst, "\n"); + long i = 0; - c = getc(src); - } + Token T; + long curly_bracket_depth = 0; + long bracket_depth = 0; + do { + if(i == len) { + T.kind = T_DONE; + T.off = i; + T.len = 0; + } else if(i+1 < len && str[i] == '/' && str[i+1] == '/') { + T.kind = T_COMMENT; + T.off = i; + while(i < len && str[i] != '\n') + i += 1; + T.len = i - T.off; + } else if(i+1 < len && str[i] == '/' && str[i+1] == '*') { + T.kind = T_COMMENT; + T.off = i; + while(1) { + while(i < len && str[i] != '*') + i += 1; + if(i == len) + break; + assert(str[i] == '*'); + i += 1; + if(str[i] == '/') { + i += 1; + break; + } + } + T.len = i - T.off; + } else if(str[i] == ' ') { + T.kind = T_SPACE; + T.off = i; + do + i += 1; + while(i < len && str[i] == ' '); + T.len = i - T.off; + } else if(str[i] == '\t') { + T.kind = T_TAB; + T.off = i; + do + i += 1; + while(i < len && str[i] == ' '); + T.len = i - T.off; + } else if(str[i] == '\n') { + T.kind = T_NEWL; + T.off = i; + do + i += 1; + while(i < len && str[i] == '\n'); + T.len = i - T.off; + } else if(str[i] == '\'' || str[i] == '\"') { + + char f = str[i]; - while(c != EOF) { - - assert(!isspace(c)); - - char hint = getc(src); - fseek(src, -1, SEEK_CUR); - - if(c == '/' && (hint == '/' || hint == '*')) { - - _Bool mline = (hint == '*'); - char comment[1024]; - int len = 0; + T.kind = f == '"' ? T_VSTR : T_VCHAR; + T.off = i; + i += 1; // Skip the '\'' or '"'. + do { + while(i < len && str[i] != f && str[i] != '\\') + i += 1; - if(len == sizeof(comment)-1) - return "Static buffer is too small to " - "hold a comment this big"; - - comment[len++] = c; - c = getc(src); - - if(c == EOF) + if(i == len || str[i] == f) break; - - if(!mline && c == '\n') - break; - - if(mline && c == '*') { - c = getc(src); - - if(c == '/') - break; - - if(len == sizeof(comment)-1) - return "Static buffer is too small to " - "hold a comment this big"; - comment[len++] = '*'; + if(str[i] == '\\') { + i += 1; // Skip the '\\'. + if(i < len) + i += 1; // ..and the character after it. } } while(1); - assert(len < (int) sizeof(comment)); - comment[len] = '\0'; - - if(c == EOF) - fprintf(dst, "%s", comment); - else if(mline) - fprintf(dst, "%s*/", comment); - else - fprintf(dst, "%s\n", comment); - } - - if(c == '"' || c == '\'') { - - char str[512]; - int len = 0; - - char f = c; - c = getc(src); - while(c != f && c != EOF) { - if(len == sizeof(str)-1) - return "Static buffer is too small " - "to hold string literal"; - str[len++] = c; - c = getc(src); + if(i < len) { + assert(str[i] == f); + i += 1; // Skip the final '\'' or '"'. } + T.len = i - T.off; - assert(len < (int) sizeof(str)); - str[len] = '\0'; - - fprintf(dst, "%s\n", str); - - } else if(isalpha(c) || c == '_') { - - char ident[128]; - int len = 0; - - do { - if(len == sizeof(ident)-1) - return "Static buffer is too small " - "to hold identifier"; - ident[len++] = c; - c = getc(src); - } while(isalpha(c) || isdigit(c) || c == '_'); - - assert(len < (int) sizeof(ident)); - ident[len] = '\0'; - - if(iskeyword(ident)) - fprintf(dst, "%s", - ident, ident); - else - fprintf(dst, "%s", ident); - - } else if(isdigit(c)) { - - char numb[64]; - int len = 0; - - do { - if(len == sizeof(numb)-1) - return "Static buffer is too small to " - "hold a number with so many digits"; - numb[len++] = c; - c = getc(src); - } while(isdigit(c)); - - // Here [c] refers to the first non-digit - // character. We want to know the following - // also. - char hint = getc(src); // Read one. - fseek(src, -1, SEEK_CUR); // Then go back by one. - - _Bool dot = 0; - if(c == '.' && isdigit(hint)) { - // Maybe it's a floating-point value? - do { - if(len == sizeof(numb)-1) - return "Static buffer is too small to " - "hold a number with so many digits"; - numb[len++] = c; - c = getc(src); - } while(isdigit(c)); - dot = 1; + } else if(isdigit(str[i])) { + T.off = i; + while(i < len && isdigit(str[i])) + i += 1; + if(i+1 < len && str[i] == '.' && isdigit(str[i+1])) { + i += 1; // Skip the '.'. + while(i < len && isdigit(str[i])) + i += 1; + T.kind = T_VFLT; + } else T.kind = T_VINT; + T.len = i - T.off; + } else if(isoperat(str[i])) { + T.kind = T_OPERATOR; + T.off = i; + while(i < len && isoperat(str[i])) + i += 1; + T.len = i - T.off; + } else if(isalpha(str[i]) || str[i] == '_') { + T.off = i; + while(isalpha(str[i]) || isdigit(str[i]) || str[i] == '_') + i += 1; + T.len = i - T.off; + if(iskword(str + T.off, T.len)) + T.kind = T_KWORD; + else { + // Is the identifier followed by + // a left parenthesis? + long k = i; + while(k < len && (str[k] == ' ' || str[k] == '\t')) + k += 1; + if(k < len && str[k] == '(') { + if(curly_bracket_depth == 0) + T.kind = T_FDECLNAME; + else + T.kind = T_FCALLNAME; + } else { + T.kind = T_IDENTIFIER; + } } - - assert(len < (int) sizeof(numb)); - numb[len] = '\0'; - - fprintf(dst, "%s", - dot ? "vl-float" : "vl-int", numb); - } else if(isop(c)) { - - char oper[32]; - int len = 0; - - do { - if(len == sizeof(oper)-1) - return "Static buffer is too small to " - "hold an operator this long"; - oper[len++] = c; - c = getc(src); - } while(isop(c)); - - assert(len < (int) sizeof(oper)); - oper[len] = '\0'; - - fprintf(dst, "%s", oper); } else { - fprintf(dst, "%c", c); - c = getc(src); + + switch(str[i]) { + case '{': curly_bracket_depth += 1; break; + case '}': curly_bracket_depth -= 1; break; + case '(': bracket_depth += 1; break; + case ')': bracket_depth -= 1; break; + } + + T.kind = str[i]; + T.off = i; + T.len = 1; + i += 1; } - while(isspace(c)) { - if(c == ' ') - fprintf(dst, " "); - else if(c == '\n') - fprintf(dst, "\n"); + if(count == capacity) { + int new_capacity; + if(capacity == 0) + new_capacity = 8; + else + new_capacity = 2 * capacity; + + void *temp = realloc(array, new_capacity * sizeof(Token)); + if(temp == NULL) { + // Uoops! Out of memory. + free(array); + return NULL; + } - c = getc(src); + array = temp; + capacity = new_capacity; } - } - return NULL; + + array[count++] = T; + + } while(T.kind != T_DONE); + return array; } -const char *c2html(const char *src, const char *dst) +typedef struct { + char *error; + char *data; + long size; + long used; +} buff_t; + +static void buff_init(buff_t *buff) { - FILE *src_fp = fopen(src, "rb"); - FILE *dst_fp = fopen(dst, "wb"); - if(src_fp == NULL || dst_fp == NULL) { - if(src_fp != NULL) fclose(src_fp); - if(dst_fp != NULL) fclose(dst_fp); + memset(buff, 0, sizeof(buff_t)); +} + +static void buff_puts(buff_t *buff, const char *str, long len) { + + if(buff->error) + return; + + if(buff->used + len > buff->size) { + + int new_size; + if(buff->size == 0) + new_size = 32; + else + new_size = 2 * buff->size; + + if(buff->used + len > new_size) + new_size = buff->used + len; + + void *temp = realloc(buff->data, new_size+1); + if(temp == NULL) { + free(buff->data); + buff->error = "Out of memory"; + return; + } + + buff->data = temp; + buff->size = new_size; } - const char *err = c2html2(src_fp, dst_fp); - fclose(src_fp); - fclose(dst_fp); - return err; + + memcpy(buff->data + buff->used, str, len); + buff->used += len; +} + +static void buff_printf(buff_t *buff, const char *fmt, ...) +{ + char maybe[512]; + char *buffer; + va_list va, va2; + va_start(va, fmt); + va_copy(va2, va); + + int n = vsnprintf(maybe, sizeof(maybe), fmt, va); + if(n < 0) { + free(buff->data); + buff->error = "Bad format"; + return; + } + + if(n < (int) sizeof(maybe)) + buffer = maybe; + else { + + buffer = malloc(n+1); + if(buffer == NULL) { + free(buff->data); + buff->error = "Out of memory"; + goto done; + } + + int k = vsnprintf(buffer, n+1, fmt, va2); + assert(k >= 0 && k == n); + } + + assert(buffer[n] == '\0'); + buff_puts(buff, buffer, n); + +done: + if(buffer != maybe) + free(buffer); + va_end(va2); + va_end(va); +} + +static void print_escaped(buff_t *buff, const char *str, long len) +{ + long j = 0; + while(1) { + + long off = j; + + while(j < len && str[j] != '<' && str[j] != '>') + j += 1; + + long end = j; + buff_puts(buff, str + off, end - off); + + if(j == len) + break; + + assert(str[j] == '<' || str[j] == '>'); + if(str[j] == '<') + buff_printf(buff, "<"); + else + buff_printf(buff, ">"); + j += 1; + } +} + +char *c2html(const char *str, long len, _Bool table_mode, const char *class_prefix, const char **error) { + + if(str == NULL) + str = ""; + + if(len < 0) + len = strlen(str); + + if(class_prefix == NULL) + class_prefix = ""; + + buff_t buff; + buff_init(&buff); + long lineno = 1; + + buff_printf(&buff, "\n" + "
\n" + "
\n", + class_prefix, class_prefix); + + if(table_mode) + buff_printf(&buff, " \n" + " \n \n
1"); + + Token *tokens = tokenize(str, len); + for(int i = 0; tokens[i].kind != T_DONE; i += 1) { + + switch(tokens[i].kind) { + + case T_DONE: + assert(0); + break; + + case T_NEWL: + if(table_mode) + for(int j = 0; j < tokens[i].len; j += 1) { + lineno += 1; + buff_printf(&buff, "
%d", lineno); + } + else { + lineno += tokens[i].len; + for(int j = 0; j < tokens[i].len; j += 1) + buff_printf(&buff, "
\n"); + } + break; + + case T_SPACE: + if(tokens[i].len == 1) + buff_printf(&buff, " ", 1); + else { + for(int j = 0; j < tokens[i].len; j += 1) + buff_printf(&buff, " "); + } + break; + + case T_TAB: + for(int j = 0; j < tokens[i].len; j += 1) + buff_printf(&buff, "    "); + break; + + case T_KWORD: + buff_printf(&buff, "%.*s", + class_prefix, class_prefix, + tokens[i].len, str + tokens[i].off, + tokens[i].len, str + tokens[i].off); + break; + + case T_VSTR: + buff_printf(&buff, "", class_prefix); + print_escaped(&buff, str + tokens[i].off, tokens[i].len); + buff_printf(&buff, ""); + break; + + case T_VCHAR: + buff_printf(&buff, "", class_prefix); + print_escaped(&buff, str + tokens[i].off, tokens[i].len); + buff_printf(&buff, ""); + break; + + case T_VINT: + buff_printf(&buff, "%.*s", + class_prefix, tokens[i].len, str + tokens[i].off); + break; + + case T_VFLT: + buff_printf(&buff, "%.*s", + class_prefix, tokens[i].len, str + tokens[i].off); + break; + + case T_FDECLNAME: + buff_printf(&buff, "%.*s", + class_prefix, class_prefix, tokens[i].len, str + tokens[i].off); + break; + + case T_FCALLNAME: + buff_printf(&buff, "%.*s", + class_prefix, class_prefix, tokens[i].len, str + tokens[i].off); + break; + + case T_IDENTIFIER: + buff_printf(&buff, "%.*s", + class_prefix, tokens[i].len, str + tokens[i].off); + break; + + case T_COMMENT: + buff_printf(&buff, "", class_prefix); + print_escaped(&buff, str + tokens[i].off, tokens[i].len); + buff_printf(&buff, ""); + break; + + case T_OPERATOR: + buff_printf(&buff, "", class_prefix); + print_escaped(&buff, str + tokens[i].off, tokens[i].len); + buff_printf(&buff, ""); + break; + + default: + buff_printf(&buff, "%c", str[tokens[i].off]); + break; + } + } + + if(table_mode) + buff_printf(&buff, "
\n"); + buff_printf(&buff, "
\n" + "
"); + + char *res; + if(buff.error == NULL) { + buff.data[buff.used] = '\0'; + res = buff.data; + } else { + if(error != NULL) + *error = buff.error; + res = NULL; + } + + free(tokens); + return res; } \ No newline at end of file diff --git a/c2html.h b/c2html.h index 3968abd..bf5d6e6 100644 --- a/c2html.h +++ b/c2html.h @@ -1,3 +1,3 @@ -#include -const char *c2html(const char *src, const char *dst); -const char *c2html2(FILE *src, FILE *dst); \ No newline at end of file + +char *c2html(const char *str, long len, _Bool table_mode, + const char *class_prefix, const char **error); diff --git a/cli.c b/cli.c index 3724f02..dbd6cf4 100644 --- a/cli.c +++ b/cli.c @@ -1,25 +1,73 @@ -#include +#include +#include #include #include "c2html.h" +static char *load_file(const char *file, long *size) +{ + FILE *fp = fopen(file, "rb"); + if(fp == NULL) + return NULL; + + fseek(fp, 0, SEEK_END); + long size2 = ftell(fp); + fseek(fp, 0, SEEK_SET); + + char *data = malloc(size2+1); + if(data == NULL) { + fclose(fp); + return NULL; + } + + fread(data, 1, size2, fp); + fclose(fp); + + if(size) + *size = size2; + + data[size2] = '\0'; + return data; +} + int main(int argc, char **argv) { - assert(argc > 0); if(argc < 3) { - fprintf(stderr, - "Missing input ad output files\n" - "\n" - "Usage:\n" - " $ %s input.c output.html\n" - "\n", argv[0]); + fprintf(stderr, "ERROR: Missing input and output file\n"); + return -1; + } + char *src_file = argv[1]; + char *dst_file = argv[2]; + + long src_size; + char *src_data = load_file(src_file, &src_size); + + if(src_data == NULL) { + fprintf(stderr, "ERROR: Failed to open \"%s\"\n", src_file); return -1; } - const char *err; + _Bool table_mode = 0; + const char *class_prefix = "c2h-"; - err = c2html(argv[1], argv[2]); - if(err != NULL) - fprintf(stderr, "ERROR: %s\n", err); - fprintf(stderr, "OK"); - return err == NULL ? -1 : 0; + const char *error; + char *dst_data = c2html(src_data, src_size, table_mode, class_prefix, &error); + + if(dst_data == NULL) + fprintf(stderr, "ERROR: %s\n", error); + else { + + FILE *fp = fopen(dst_file, "wb"); + if(fp == NULL) + fprintf(stderr, "ERROR: Failed to open \"%s\"\n", dst_file); + else { + fwrite(dst_data, 1, strlen(dst_data), fp); + fclose(fp); + fprintf(stderr, "OK\n"); + } + + free(dst_data); + } + + free(src_data); + return 0; } \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..7f09964 --- /dev/null +++ b/style.css @@ -0,0 +1,80 @@ + + div.c2h-code { + max-height: 80vh; + margin: 50px; + border-radius: 3px; + color: white; + font-family: monospace; + font-size: 16px; + background: hsl(210, 15%, 22%); + padding: 10px; + } + + div.c2h-code-inner::-webkit-scrollbar { + width: 10px; + background: transparent; + } + + div.c2h-code-inner::-webkit-scrollbar-thumb { + background: hsla(210, 13%, 40%, 0.7); + } + + div.c2h-code-inner { + overflow: auto; + max-height: inherit; + scrollbar-color: hsla(210, 13%, 40%, 0.7) transparent; + } + + div.c2h-code table { + border-collapse: collapse; + font-size: inherit; + } + + div.c2h-code table td { + color: white; + } + + div.c2h-code table td:first-child { + text-align: right; + padding: 0 10px; + color: hsla(210, 13%, 40%, 0.7); + } + + .c2h-comment { + color: hsl(221, 12%, 69%); + } + + .c2h-val-int, + .c2h-val-flt { + color: hsl(32, 93%, 66%); + } + + .c2h-val-str, + .c2h-val-char { + color: hsl(114, 31%, 68%); + } + + .c2h-kword { + color: hsl(300, 30%, 68%); + } + + .c2h-kword-static, + .c2h-kword-const { + color: hsl(357, 79%, 65%); + } + + .c2h-operator { + color: hsl(13, 93%, 66%); + } + + .c2h-identifier { + color: hsl(219, 28%, 88%); + } + + .c2h-fdeclname { + color: hsl(180, 36%, 54%); + } + + .c2h-fcallname { + color: hsl(210, 50%, 60%); + } \ No newline at end of file