644 lines
19 KiB
C
644 lines
19 KiB
C
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
* +------------------------------------------------------------+ *
|
|
* | This is free and unencumbered software released into the | *
|
|
* | public domain. | *
|
|
* | | *
|
|
* | Anyone is free to copy, modify, publish, use, compile, | *
|
|
* | sell, or distribute this software, either in source code | *
|
|
* | form or as a compiled binary, for any purpose, commercial | *
|
|
* | or non-commercial, and by any means. | *
|
|
* | | *
|
|
* | In jurisdictions that recognize copyright laws, the author | *
|
|
* | or authors of this software dedicate any and all copyright | *
|
|
* | interest in the software to the public domain. We make | *
|
|
* | this dedication for the benefit of the public at large and | *
|
|
* | to the detriment of our heirs and successors. We intend | *
|
|
* | this dedication to be an overt act of relinquishment in | *
|
|
* | perpetuity of all present and future rights to this | *
|
|
* | software under copyright law. | *
|
|
* | | *
|
|
* | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF | *
|
|
* | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED | *
|
|
* | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A | *
|
|
* | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | *
|
|
* | THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | *
|
|
* | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | *
|
|
* | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH | *
|
|
* | THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | * *
|
|
* +------------------------------------------------------------+--+ *
|
|
* | For more information, please refer to <http://unlicense.org/> | *
|
|
* +---------------------------------------------------------------+ *
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
|
|
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,
|
|
T_DIRECTIVE,
|
|
} Kind;
|
|
|
|
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",
|
|
"const", "continue", "default",
|
|
"do", "double", "else", "enum",
|
|
"extern", "float", "for", "goto",
|
|
"if", "int", "long", "register",
|
|
"return", "short", "signed",
|
|
"sizeof", "static", "struct",
|
|
"switch", "typedef", "union",
|
|
"unsigned", "void", "volatile",
|
|
"while"
|
|
};
|
|
const int num_keywords = sizeof(keywords)/sizeof(keywords[0]);
|
|
for(int i = 0; i < num_keywords; i += 1)
|
|
if(strlen(keywords[i]) == len && !strncmp(keywords[i], str, len))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static Token *tokenize(const char *str, long len)
|
|
{
|
|
Token *array = NULL;
|
|
int count = 0, capacity = 0;
|
|
|
|
long i = 0;
|
|
|
|
Token T;
|
|
long curly_bracket_depth = 0;
|
|
bool only_spaces_since_line_start = 1;
|
|
bool prev_nonspace_was_directive = 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];
|
|
|
|
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(i == len || str[i] == f)
|
|
break;
|
|
|
|
if(str[i] == '\\') {
|
|
i += 1; // Skip the '\\'.
|
|
if(i < len)
|
|
i += 1; // ..and the character after it.
|
|
}
|
|
|
|
} while(1);
|
|
|
|
if(i < len) {
|
|
assert(str[i] == f);
|
|
i += 1; // Skip the final '\'' or '"'.
|
|
}
|
|
T.len = i - T.off;
|
|
|
|
} else if(isdigit(str[i])) {
|
|
|
|
T.off = i;
|
|
|
|
// We allow an 'x' if it's after a '0'.
|
|
if(i+2 < len && str[i] == '0' && str[i+1] == 'x' && isdigit(str[i+2]))
|
|
i += 2; // Skip the '0x'.
|
|
|
|
while(i < len && isdigit(str[i]))
|
|
i += 1;
|
|
|
|
// If the next character is a dot followed
|
|
// by a digit, then we continue to scan.
|
|
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(isalpha(str[i]) || str[i] == '_') {
|
|
|
|
T.off = i;
|
|
while(isalpha(str[i]) || isdigit(str[i]) || str[i] == '_')
|
|
i += 1;
|
|
T.len = i - T.off;
|
|
|
|
/* It may either be an identifier or a
|
|
* language keyword.
|
|
*/
|
|
|
|
if(iskword(str + T.off, T.len))
|
|
T.kind = T_KWORD;
|
|
else {
|
|
|
|
/* If the identifier is followed by a '(' and
|
|
* it's in the global scope, then it's in a
|
|
* function definiton. If it's not in the global
|
|
* scope then it's a function call.
|
|
* Between the identifier and the '(' there may
|
|
* be some whitespace. An exception is made if
|
|
* before the identifier comes a preprocessor
|
|
* directive, in which case the '(' must come
|
|
* right after the identifier.
|
|
*/
|
|
|
|
bool followed_by_parenthesis = 0;
|
|
bool yes_and_immediately = 0;
|
|
{
|
|
long k = i;
|
|
while(k < len && (str[k] == ' ' || str[k] == '\t'))
|
|
k += 1;
|
|
|
|
if(k < len && str[k] == '(') {
|
|
followed_by_parenthesis = 1;
|
|
if(k == i)
|
|
yes_and_immediately = 1;
|
|
}
|
|
}
|
|
|
|
if(followed_by_parenthesis) {
|
|
if(curly_bracket_depth == 0) {
|
|
if(prev_nonspace_was_directive) {
|
|
if(yes_and_immediately)
|
|
T.kind = T_FDECLNAME;
|
|
else
|
|
T.kind = T_IDENTIFIER;
|
|
} else
|
|
T.kind = T_FDECLNAME;
|
|
} else
|
|
T.kind = T_FCALLNAME;
|
|
} else {
|
|
T.kind = T_IDENTIFIER;
|
|
}
|
|
}
|
|
|
|
} else if(str[i] == '#' && only_spaces_since_line_start) {
|
|
|
|
// The first non-whitespace token of the line
|
|
// is a '#'. If it's followed by an alphabetical
|
|
// character, then it's a directive. (There may
|
|
// be whitespace between the '#' and the identifier)
|
|
|
|
long j = i; // Use a secondary cursor to explore
|
|
// what's after the '#'.
|
|
|
|
j += 1; // Skip the '#'.
|
|
|
|
// Skip spaces after the '#', if there are any.
|
|
while(j < len && (str[j] == ' ' || str[j] == '\t'))
|
|
j += 1;
|
|
|
|
if(isalpha(str[j])) {
|
|
|
|
// It's a preprocessor directive!
|
|
|
|
T.kind = T_DIRECTIVE;
|
|
T.off = i;
|
|
|
|
while(j < len && isalpha(str[j]))
|
|
j += 1;
|
|
|
|
T.len = j - T.off;
|
|
|
|
i = j;
|
|
|
|
} else {
|
|
// Wasn't a directive.. Just tokenize the '#'.
|
|
T.kind = '#';
|
|
T.off = i;
|
|
T.len = 1;
|
|
i += 1;
|
|
}
|
|
|
|
} else if(str[i] == '<' && prev_nonspace_was_directive) {
|
|
|
|
T.kind = T_VSTR;
|
|
T.off = i;
|
|
while(i < len && str[i] != '>')
|
|
i += 1;
|
|
if(i < len)
|
|
i += 1; // Skip the '>'.
|
|
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 {
|
|
|
|
switch(str[i]) {
|
|
case '{': curly_bracket_depth += 1; break;
|
|
case '}': curly_bracket_depth -= 1; break;
|
|
}
|
|
|
|
T.kind = str[i];
|
|
T.off = i;
|
|
T.len = 1;
|
|
i += 1;
|
|
}
|
|
|
|
if(T.kind == T_NEWL)
|
|
only_spaces_since_line_start = 1;
|
|
else
|
|
if(T.kind != T_TAB && T.kind != T_SPACE)
|
|
only_spaces_since_line_start = 0;
|
|
|
|
if(T.kind == T_DIRECTIVE)
|
|
prev_nonspace_was_directive = 1;
|
|
else
|
|
if(T.kind != T_TAB && T.kind != T_SPACE)
|
|
prev_nonspace_was_directive = 0;
|
|
|
|
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;
|
|
}
|
|
|
|
array = temp;
|
|
capacity = new_capacity;
|
|
}
|
|
|
|
array[count++] = T;
|
|
|
|
} while(T.kind != T_DONE);
|
|
return array;
|
|
}
|
|
|
|
typedef struct {
|
|
char *error;
|
|
char *data;
|
|
long size;
|
|
long used;
|
|
} buff_t;
|
|
|
|
static void buff_init(buff_t *buff)
|
|
{
|
|
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;
|
|
}
|
|
|
|
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] != '>'
|
|
&& str[j] != ' '
|
|
&& str[j] != '\n'
|
|
&& str[j] != '\t')
|
|
j += 1;
|
|
|
|
long end = j;
|
|
buff_puts(buff, str + off, end - off);
|
|
|
|
if(j == len)
|
|
break;
|
|
|
|
switch(str[j]) {
|
|
case '<': buff_printf(buff, "<"); break;
|
|
case '>': buff_printf(buff, ">"); break;
|
|
case '\n': buff_printf(buff, "<br />\n"); break;
|
|
case '\t': buff_printf(buff, "    "); break;
|
|
|
|
case ' ':
|
|
if(j == 0 || str[j-1] != ' ')
|
|
buff_printf(buff, " ");
|
|
else
|
|
buff_printf(buff, " ");
|
|
break;
|
|
|
|
default: assert(0);
|
|
}
|
|
|
|
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,
|
|
"<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) {
|
|
|
|
if(!table_mode)
|
|
if(i == 0 || tokens[i-1].kind == T_NEWL)
|
|
buff_printf(&buff, " ");
|
|
|
|
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, " ");
|
|
}
|
|
break;
|
|
|
|
case T_TAB:
|
|
for(int j = 0; j < tokens[i].len; j += 1)
|
|
buff_printf(&buff, "    ");
|
|
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;
|
|
|
|
case T_DIRECTIVE:
|
|
buff_printf(&buff, "<span class=\"%sdirective\">", 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;
|
|
} |