This commit is contained in:
cozis
2022-05-05 14:35:47 +02:00
parent 6337a7f7ca
commit cd8f9fd6a7
7 changed files with 578 additions and 205 deletions
+1 -1
View File
@@ -1 +1 @@
c2html c2h
+2
View File
@@ -0,0 +1,2 @@
# c2html
A tool to add HTML syntax highlighting to C code.
+1 -1
View File
@@ -1 +1 @@
gcc cli.c c2html.c -o c2html -Wall -Wextra gcc cli.c c2html.c -o c2h -Wall -Wextra -g
+429 -186
View File
@@ -1,17 +1,48 @@
#include <stdbool.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <assert.h> #include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <ctype.h> #include <ctype.h>
#include "c2html.h"
/* Converts files containing C code to an typedef enum {
* HTML version with syntax highlighting. T_DONE = 256,
* The highlighting is best-effort, it's T_COMMENT,
* only based on the tokenization result T_SPACE,
* and doesn't do a full parsing. Parsing T_TAB,
* C would blow up the code of this utility. 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[] = { static const char *keywords[] = {
"auto", "break", "case", "char", "auto", "break", "case", "char",
@@ -27,210 +58,422 @@ static _Bool iskeyword(const char *str)
}; };
const int num_keywords = sizeof(keywords)/sizeof(keywords[0]); const int num_keywords = sizeof(keywords)/sizeof(keywords[0]);
for(int i = 0; i < num_keywords; i += 1) 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 1;
return 0; return 0;
} }
static _Bool isop(char c) static Token *tokenize(const char *str, long len)
{ {
static const char ops[] = ".<>=!+-*/%&^|;~?:"; Token *array = NULL;
for(int i = 0; ops[i] != '\0'; i += 1) int count = 0, capacity = 0;
if(c == ops[i])
return 1;
return 0;
}
const char *c2html2(FILE *src, FILE *dst) long i = 0;
{
char c = getc(src);
while(isspace(c)) {
if(c == ' ')
fprintf(dst, " ");
else if(c == '\n')
fprintf(dst, "\n");
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) { T.kind = f == '"' ? T_VSTR : T_VCHAR;
T.off = i;
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;
i += 1; // Skip the '\'' or '"'.
do { do {
while(i < len && str[i] != f && str[i] != '\\')
i += 1;
if(len == sizeof(comment)-1) if(i == len || str[i] == f)
return "Static buffer is too small to "
"hold a comment this big";
comment[len++] = c;
c = getc(src);
if(c == EOF)
break; break;
if(!mline && c == '\n')
break;
if(mline && c == '*') {
c = getc(src); if(str[i] == '\\') {
i += 1; // Skip the '\\'.
if(c == '/') if(i < len)
break; i += 1; // ..and the character after it.
if(len == sizeof(comment)-1)
return "Static buffer is too small to "
"hold a comment this big";
comment[len++] = '*';
} }
} while(1); } while(1);
assert(len < (int) sizeof(comment)); if(i < len) {
comment[len] = '\0'; assert(str[i] == f);
i += 1; // Skip the final '\'' or '"'.
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);
} }
T.len = i - T.off;
assert(len < (int) sizeof(str)); } else if(isdigit(str[i])) {
str[len] = '\0'; T.off = i;
while(i < len && isdigit(str[i]))
fprintf(dst, "<span class=\"vl-string\">%s</span>\n", str); i += 1;
if(i+1 < len && str[i] == '.' && isdigit(str[i+1])) {
} else if(isalpha(c) || c == '_') { i += 1; // Skip the '.'.
while(i < len && isdigit(str[i]))
char ident[128]; i += 1;
int len = 0; T.kind = T_VFLT;
} else T.kind = T_VINT;
do { T.len = i - T.off;
if(len == sizeof(ident)-1) } else if(isoperat(str[i])) {
return "Static buffer is too small " T.kind = T_OPERATOR;
"to hold identifier"; T.off = i;
ident[len++] = c; while(i < len && isoperat(str[i]))
c = getc(src); i += 1;
} while(isalpha(c) || isdigit(c) || c == '_'); T.len = i - T.off;
} else if(isalpha(str[i]) || str[i] == '_') {
assert(len < (int) sizeof(ident)); T.off = i;
ident[len] = '\0'; while(isalpha(str[i]) || isdigit(str[i]) || str[i] == '_')
i += 1;
if(iskeyword(ident)) T.len = i - T.off;
fprintf(dst, "<span class=\"keyword kw-%s\">%s</span>", if(iskword(str + T.off, T.len))
ident, ident); T.kind = T_KWORD;
else else {
fprintf(dst, "<span class=\"ident\">%s</span>", ident); // Is the identifier followed by
// a left parenthesis?
} else if(isdigit(c)) { long k = i;
while(k < len && (str[k] == ' ' || str[k] == '\t'))
char numb[64]; k += 1;
int len = 0; if(k < len && str[k] == '(') {
if(curly_bracket_depth == 0)
do { T.kind = T_FDECLNAME;
if(len == sizeof(numb)-1) else
return "Static buffer is too small to " T.kind = T_FCALLNAME;
"hold a number with so many digits"; } else {
numb[len++] = c; T.kind = T_IDENTIFIER;
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;
} }
assert(len < (int) sizeof(numb));
numb[len] = '\0';
fprintf(dst, "<span class=\"%s\">%s</span>",
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, "<span class=\"oper\">%s</span>", oper);
} else { } 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(count == capacity) {
if(c == ' ') int new_capacity;
fprintf(dst, " "); if(capacity == 0)
else if(c == '\n') new_capacity = 8;
fprintf(dst, "\n"); 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"); memset(buff, 0, sizeof(buff_t));
FILE *dst_fp = fopen(dst, "wb"); }
if(src_fp == NULL || dst_fp == NULL) {
if(src_fp != NULL) fclose(src_fp); static void buff_puts(buff_t *buff, const char *str, long len) {
if(dst_fp != NULL) fclose(dst_fp);
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); memcpy(buff->data + buff->used, str, len);
fclose(dst_fp); buff->used += len;
return err; }
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, "&lt;");
else
buff_printf(buff, "&gt;");
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"
"<div class=\"%scode\">\n"
" <div class=\"%scode-inner\">\n",
class_prefix, class_prefix);
if(table_mode)
buff_printf(&buff, " <table>\n"
" <tr><td>1</td><td>");
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, "</td></tr>\n <tr><td>%d</td><td>", lineno);
}
else {
lineno += tokens[i].len;
for(int j = 0; j < tokens[i].len; j += 1)
buff_printf(&buff, "<br />\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, "&emsp;");
}
break;
case T_TAB:
for(int j = 0; j < tokens[i].len; j += 1)
buff_printf(&buff, "&emsp;&emsp;&emsp;&emsp;");
break;
case T_KWORD:
buff_printf(&buff, "<span class=\"%skword %skword-%.*s\">%.*s</span>",
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, "<span class=\"%sval-str\">", class_prefix);
print_escaped(&buff, str + tokens[i].off, tokens[i].len);
buff_printf(&buff, "</span>");
break;
case T_VCHAR:
buff_printf(&buff, "<span class=\"%sval-char\">", class_prefix);
print_escaped(&buff, str + tokens[i].off, tokens[i].len);
buff_printf(&buff, "</span>");
break;
case T_VINT:
buff_printf(&buff, "<span class=\"%sval-int\">%.*s</span>",
class_prefix, tokens[i].len, str + tokens[i].off);
break;
case T_VFLT:
buff_printf(&buff, "<span class=\"%sval-flt\">%.*s</span>",
class_prefix, tokens[i].len, str + tokens[i].off);
break;
case T_FDECLNAME:
buff_printf(&buff, "<span class=\"%sidentifier %sfdeclname\">%.*s</span>",
class_prefix, class_prefix, tokens[i].len, str + tokens[i].off);
break;
case T_FCALLNAME:
buff_printf(&buff, "<span class=\"%sidentifier %sfcallname\">%.*s</span>",
class_prefix, class_prefix, tokens[i].len, str + tokens[i].off);
break;
case T_IDENTIFIER:
buff_printf(&buff, "<span class=\"%sidentifier\">%.*s</span>",
class_prefix, tokens[i].len, str + tokens[i].off);
break;
case T_COMMENT:
buff_printf(&buff, "<span class=\"%scomment\">", class_prefix);
print_escaped(&buff, str + tokens[i].off, tokens[i].len);
buff_printf(&buff, "</span>");
break;
case T_OPERATOR:
buff_printf(&buff, "<span class=\"%soperator\">", class_prefix);
print_escaped(&buff, str + tokens[i].off, tokens[i].len);
buff_printf(&buff, "</span>");
break;
default:
buff_printf(&buff, "%c", str[tokens[i].off]);
break;
}
}
if(table_mode)
buff_printf(&buff, "</td></tr>\n </table>\n");
buff_printf(&buff, " </div>\n"
"</div>");
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;
} }
+3 -3
View File
@@ -1,3 +1,3 @@
#include <stdio.h>
const char *c2html(const char *src, const char *dst); char *c2html(const char *str, long len, _Bool table_mode,
const char *c2html2(FILE *src, FILE *dst); const char *class_prefix, const char **error);
+62 -14
View File
@@ -1,25 +1,73 @@
#include <assert.h> #include <string.h>
#include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include "c2html.h" #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) int main(int argc, char **argv)
{ {
assert(argc > 0);
if(argc < 3) { if(argc < 3) {
fprintf(stderr, fprintf(stderr, "ERROR: Missing input and output file\n");
"Missing input ad output files\n" return -1;
"\n" }
"Usage:\n" char *src_file = argv[1];
" $ %s input.c output.html\n" char *dst_file = argv[2];
"\n", argv[0]);
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; return -1;
} }
const char *err; _Bool table_mode = 0;
const char *class_prefix = "c2h-";
err = c2html(argv[1], argv[2]); const char *error;
if(err != NULL) char *dst_data = c2html(src_data, src_size, table_mode, class_prefix, &error);
fprintf(stderr, "ERROR: %s\n", err);
fprintf(stderr, "OK"); if(dst_data == NULL)
return err == NULL ? -1 : 0; 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;
} }
+80
View File
@@ -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%);
}