Telodendria/src/HeaderParser.c
Jordan Bancino 2447bb63cc Add hdoc, a simple tool for generating documentation from a C header.
This is a very early prototype. It works, but it is probably not efficient
or reliable. However, the documentation format it parses is stable, so I
will begin moving the documentation into the headers.
2023-04-27 01:34:49 +00:00

552 lines
13 KiB
C

#include <HeaderParser.h>
#include <Memory.h>
#include <string.h>
#include <ctype.h>
static int
HeaderConsumeWhitespace(HeaderExpr * expr)
{
int c;
while (1)
{
c = StreamGetc(expr->state.stream);
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
{
expr->type = HP_EOF;
expr->data.error.msg = "End of stream reached.";
expr->data.error.lineNo = expr->state.lineNo;
break;
}
if (isspace(c))
{
if (c == '\n')
{
expr->state.lineNo++;
}
}
else
{
break;
}
}
return c;
}
static char *
HeaderConsumeWord(HeaderExpr * expr)
{
char *str = Malloc(16 * sizeof(char));
int len = 16;
int i;
int c;
if (!str)
{
return NULL;
}
c = HeaderConsumeWhitespace(expr);
i = 0;
str[i] = c;
i++;
while (!isspace(c = StreamGetc(expr->state.stream)))
{
if (i >= len)
{
len *= 2;
str = Realloc(str, len * sizeof(char));
}
str[i] = c;
i++;
}
if (i >= len)
{
len++;
str = Realloc(str, len * sizeof(char));
}
str[i] = '\0';
if (c != EOF)
{
StreamUngetc(expr->state.stream, c);
}
return str;
}
static char *
HeaderConsumeAlnum(HeaderExpr * expr)
{
char *str = Malloc(16 * sizeof(char));
int len = 16;
int i;
int c;
if (!str)
{
return NULL;
}
c = HeaderConsumeWhitespace(expr);
i = 0;
str[i] = c;
i++;
while (isalnum(c = StreamGetc(expr->state.stream)))
{
if (i >= len)
{
len *= 2;
str = Realloc(str, len * sizeof(char));
}
str[i] = c;
i++;
}
if (i >= len)
{
len++;
str = Realloc(str, len * sizeof(char));
}
str[i] = '\0';
if (c != EOF)
{
StreamUngetc(expr->state.stream, c);
}
return str;
}
static char *
HeaderConsumeArg(HeaderExpr * expr)
{
char *str = Malloc(16 * sizeof(char));
int len = 16;
int i;
int c;
int block = 0;
if (!str)
{
return NULL;
}
c = HeaderConsumeWhitespace(expr);
i = 0;
str[i] = c;
i++;
while (((c = StreamGetc(expr->state.stream)) != ',' && c != ')') || block > 0)
{
if (i >= len)
{
len *= 2;
str = Realloc(str, len * sizeof(char));
}
str[i] = c;
i++;
if (c == '(')
{
block++;
}
else if (c == ')')
{
block--;
}
}
if (i >= len)
{
len++;
str = Realloc(str, len * sizeof(char));
}
str[i] = '\0';
if (c != EOF)
{
StreamUngetc(expr->state.stream, c);
}
return str;
}
void
HeaderParse(Stream * stream, HeaderExpr * expr)
{
int c;
if (!expr)
{
return;
}
if (!stream)
{
expr->type = HP_PARSE_ERROR;
expr->data.error.msg = "NULL pointer to stream.";
expr->data.error.lineNo = -1;
return;
}
if (expr->type == HP_DECLARATION && expr->data.declaration.args)
{
size_t i;
for (i = 0; i < ArraySize(expr->data.declaration.args); i++)
{
Free(ArrayGet(expr->data.declaration.args, i));
}
ArrayFree(expr->data.declaration.args);
}
expr->state.stream = stream;
if (!expr->state.lineNo)
{
expr->state.lineNo = 1;
}
c = HeaderConsumeWhitespace(expr);
if (StreamEof(stream) || StreamError(stream))
{
expr->type = HP_EOF;
expr->data.error.msg = "End of stream reached.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
if (c == '/')
{
int i = 0;
c = StreamGetc(expr->state.stream);
if (c != '*')
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Expected comment opening.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
expr->type = HP_COMMENT;
while (1)
{
if (i >= HEADER_EXPR_MAX - 1)
{
expr->type = HP_PARSE_ERROR;
expr->data.error.msg = "Memory limit exceeded while parsing comment.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
c = StreamGetc(expr->state.stream);
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Unterminated comment.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
if (c == '*')
{
c = StreamGetc(expr->state.stream);
if (c == '/')
{
expr->data.text[i] = '\0';
break;
}
else
{
expr->data.text[i] = '*';
i++;
expr->data.text[i] = c;
i++;
}
}
else
{
expr->data.text[i] = c;
i++;
if (c == '\n')
{
expr->state.lineNo++;
}
}
}
}
else if (c == '#')
{
int i = 0;
char *word;
expr->type = HP_PREPROCESSOR_DIRECTIVE;
expr->data.text[i] = '#';
i++;
word = HeaderConsumeWord(expr);
strncpy(expr->data.text + i, word, HEADER_EXPR_MAX - i - 1);
i += strlen(word);
if (strcmp(word, "include") == 0 ||
strcmp(word, "undef") == 0 ||
strcmp(word, "ifdef") == 0 ||
strcmp(word, "ifndef") == 0)
{
/* Read one more word */
Free(word);
word = HeaderConsumeWord(expr);
if (i + strlen(word) + 1 >= HEADER_EXPR_MAX)
{
expr->type = HP_PARSE_ERROR;
expr->data.error.msg = "Memory limit reached parsing preprocessor directive.";
expr->data.error.lineNo = expr->state.lineNo;
}
else
{
strncpy(expr->data.text + i + 1, word, HEADER_EXPR_MAX - i - 1);
expr->data.text[i] = ' ';
}
Free(word);
}
else if (strcmp(word, "define") == 0 ||
strcmp(word, "if") == 0 ||
strcmp(word, "elif") == 0 ||
strcmp(word, "error") == 0)
{
Free(word);
expr->data.text[i] = ' ';
i++;
while (1)
{
if (i >= HEADER_EXPR_MAX - 1)
{
expr->type = HP_PARSE_ERROR;
expr->data.error.msg = "Memory limit reached parsing preprocessor directive.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
c = StreamGetc(expr->state.stream);
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Unterminated preprocessor directive.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
/* TODO: Handle backslash escapes */
if (c == '\n')
{
expr->data.text[i] = '\0';
break;
}
else
{
expr->data.text[i] = c;
i++;
}
}
}
else if (strcmp(word, "else") == 0 ||
strcmp(word, "endif") == 0)
{
/* Read no more words, that's the whole directive */
}
else
{
Free(word);
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Unknown preprocessor directive.";
expr->data.error.lineNo = expr->state.lineNo;
}
}
else
{
char *word;
StreamUngetc(expr->state.stream, c);
word = HeaderConsumeWord(expr);
if (strcmp(word, "typedef") == 0)
{
int block = 0;
int i = 0;
expr->type = HP_TYPEDEF;
strncpy(expr->data.text, word, HEADER_EXPR_MAX - 1);
i += strlen(word);
expr->data.text[i] = ' ';
i++;
while (1)
{
if (i >= HEADER_EXPR_MAX - 1)
{
expr->type = HP_PARSE_ERROR;
expr->data.error.msg = "Memory limit exceeded while parsing typedef.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
c = StreamGetc(expr->state.stream);
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Unterminated typedef.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
expr->data.text[i] = c;
i++;
if (c == '{')
{
block++;
}
else if (c == '}')
{
block--;
}
if (block <= 0 && c == ';')
{
expr->data.text[i] = '\0';
break;
}
}
}
else if (strcmp(word, "extern") == 0)
{
int wordLimit = sizeof(expr->data.declaration.returnType) - 8;
int wordLen;
Free(word);
word = HeaderConsumeWord(expr);
wordLen = strlen(word);
if (wordLen > wordLimit)
{
expr->type = HP_PARSE_ERROR;
expr->data.error.msg = "Return of declaration exceeds length limit.";
expr->data.error.lineNo = expr->state.lineNo;
}
else
{
int i = wordLen;
expr->type = HP_DECLARATION;
strncpy(expr->data.declaration.returnType, word, wordLimit);
Free(word);
c = HeaderConsumeWhitespace(expr);
if (c == '*')
{
expr->data.declaration.returnType[i] = ' ';
i++;
expr->data.declaration.returnType[i] = '*';
i++;
while ((c = HeaderConsumeWhitespace(expr)) == '*')
{
expr->data.declaration.returnType[i] = c;
i++;
}
}
StreamUngetc(expr->state.stream, c);
word = HeaderConsumeAlnum(expr);
wordLen = strlen(word);
wordLimit = sizeof(expr->data.declaration.name) - 1;
if (wordLen > wordLimit)
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Function name too long.";
expr->data.error.lineNo = expr->state.lineNo;
}
else
{
strncpy(expr->data.declaration.name, word, wordLimit);
Free(word);
word = NULL;
c = HeaderConsumeWhitespace(expr);
if (c != '(')
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Expected '('";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
expr->data.declaration.args = ArrayCreate();
do {
word = HeaderConsumeArg(expr);
ArrayAdd(expr->data.declaration.args, word);
word = NULL;
}
while ((!StreamEof(expr->state.stream)) && ((c = HeaderConsumeWhitespace(expr)) != ')'));
if (StreamEof(expr->state.stream))
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "End of file reached before ')'.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
c = HeaderConsumeWhitespace(expr);
if (c != ';')
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Expected ';'.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
}
}
}
else
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Expected comment, typedef, or extern.";
expr->data.error.lineNo = expr->state.lineNo;
}
Free(word);
}
}