gemtext-parser/gemtext-parser.c

439 lines
11 KiB
C
Raw Normal View History

2023-10-07 13:24:32 -04:00
#include <errno.h> // errno
2023-10-06 01:16:55 -04:00
#include <stddef.h> // NULL, size_t
#include <stdio.h> // fclose
#include <stdlib.h> // calloc, free
#include <string.h> // memcpy
#include "gemtext-parser.h"
2023-10-08 11:59:17 -04:00
int lineBufferInit(lineBuffer *lb) {
char *buf = calloc(1, LBUF_SIZE);
if (buf == NULL) return 2;
lb->len = 0;
lb->capacity = LBUF_SIZE;
lb->buf = buf;
lb->cursor = buf;
return 0;
}
2023-10-06 01:16:55 -04:00
gemtextParser* gemtextParserInit(FILE *stream) {
gemtextParser *parser = calloc(1, sizeof(gemtextParser));
if (parser == NULL)
return NULL;
parser->stream = stream;
parser->mode = normalMode;
parser->state = lineStart;
2023-10-08 11:59:17 -04:00
if (lineBufferInit(&parser->buffer) != 0) {
free(parser);
return NULL;
}
parser->linkUrl = NULL;
2023-10-06 01:16:55 -04:00
return parser;
}
void gemtextParserDeinit(gemtextParser *parser) {
fclose(parser->stream);
2023-10-08 11:59:17 -04:00
free(parser->buffer.buf);
if (parser->linkUrl != NULL) {
free(parser->linkUrl);
}
2023-10-06 01:16:55 -04:00
free(parser);
}
int gemtextLineQueueInit(gemtextLineQueue *queue) {
int ret;
queue->head = NULL;
queue->tail = NULL;
ret = pthread_mutex_init(&queue->mutex, NULL);
if (ret != 0)
return ret;
return pthread_cond_init(&queue->cond, NULL);
}
void gemtextLineQueuePush(gemtextLineQueue *queue, gemtextLine *line) {
pthread_mutex_lock(&queue->mutex);
if (queue->tail == NULL) {
queue->tail = queue->head = line;
} else {
line->next = queue->tail;
queue->tail->prev = line;
queue->tail = line;
}
queue->count++;
pthread_mutex_unlock(&queue->mutex);
}
gemtextLine* gemtextLineQueuePop(gemtextLineQueue *lq) {
gemtextLine *line;
while (lq->count == 0)
pthread_cond_wait(&lq->cond, &lq->mutex);
pthread_mutex_lock(&lq->mutex);
lq->count++;
line = lq->head;
if (line->lineType == endOfStream)
return line;
if (lq->tail == lq->head) {
lq->tail = lq->head = NULL;
} else {
lq->head = lq->head->prev;
}
pthread_mutex_unlock(&lq->mutex);
line->prev = line->next = NULL;
return line;
}
int lineBufferExtend(lineBuffer *lb, size_t len) {
char *buf = calloc(1, lb->capacity + len);
if (buf == NULL) return 2;
memcpy(buf, lb->buf, lb->len);
free(lb->buf);
lb->buf = buf;
lb->cursor = buf + lb->len;
return 0;
}
int lineBufferAppendChar(lineBuffer *lb, char c) {
int ret = 0;
if (lb->len >= lb->capacity - 1) {
ret = lineBufferExtend(lb, LBUF_SIZE);
if (ret != 0) return ret;
}
*lb->cursor = c;
lb->cursor++;
lb->len++;
return ret;
}
void lineBufferAppendCharUnchecked(lineBuffer *lb, char c) {
*lb->cursor = c;
lb->cursor++;
lb->len++;
}
int lineBufferAppendString(lineBuffer *lb, char *c, size_t len) {
int ret = 0, i = 0;
size_t rem = 0;
// Find the remaining length
rem = lb->capacity - lb->len;
// if the length won't fit our string, extend the buffer.
// We do len - rem + LBUF_SIZE for a safety margin
if (rem < len) {
ret = lineBufferExtend(lb, len - rem + LBUF_SIZE);
if (ret != 0) return ret;
}
for (i = 0; i < len; i++) {
// We use 'unchecked' because we did our checks above
lineBufferAppendCharUnchecked(lb, *c);
c++;
}
return ret;
}
void lineBufferReset(lineBuffer *lb) {
lb->len = 0;
lb->cursor = lb->buf;
}
2023-10-08 11:59:17 -04:00
int gemtextParserSend(gemtextParser *parser, gemtextLineType lt, gemtextLineQueue *lq) {
gemtextLine *line;
char *buf;
line = calloc(1, sizeof(gemtextLine));
if (line == NULL) return errno;
line->lineType = lt;
buf = strndup(parser->buffer.buf, parser->buffer.len);
if (buf == NULL) return errno;
line->str = buf;
gemtextLineQueuePush(lq, line);
lineBufferReset(&parser->buffer);
parser->state = lineStart;
parser->mode = normalMode;
return 0;
}
void logParseError(int err) {
//todo
}
2023-10-06 01:16:55 -04:00
gemtextLink* readLink(FILE *stream, lineBuffer *lb) {
char c;
2023-10-07 13:24:32 -04:00
char *buf;
2023-10-06 01:16:55 -04:00
int ret = 0;
gemtextLink *link = calloc(1, sizeof(gemtextLink));
if (link == NULL) return NULL;
while (1) {
fread(&c, 1, 1, stream);
switch (c) {
case ' ':
case '\t':
if (lb->len == 0)
continue;
2023-10-07 13:24:32 -04:00
buf = strndup(lb->buf, lb->len);
link->url = buf;
2023-10-06 01:16:55 -04:00
break;
case '\n':
if (lb->len == 0) {
free(link);
return NULL;
}
2023-10-07 13:24:32 -04:00
buf = strndup(lb->buf, lb->len);
link->url = buf;
2023-10-06 01:16:55 -04:00
return link;
default:
ret = lineBufferAppendChar(lb, c);
if (ret != 0) {
free(link);
return NULL;
}
}
}
lineBufferReset(lb);
while (1) {
fread(&c, 1, 1, stream);
switch (c) {
case '\n':
link->display = strndup(lb->buf, lb->len);
break;
default:
ret = lineBufferAppendChar(lb, c);
if (ret != 0) {
free(link->url);
free(link);
return NULL;
}
}
}
return link;
}
2023-10-08 11:59:17 -04:00
enterH1Mode(gemtextParser *parser, char c) {
lineBufferReset(&parser->buffer);
switch (c) {
case ' ':
case '\t':
break;
default:
lineBufferReset(&parser->buffer);
lineBufferAppendCharUnchecked(&parser->buffer, c);
}
parser->mode = h1Mode;
parser->state = normalState;
}
enterH2Mode(gemtextParser *parser, char c) {
lineBufferReset(&parser->buffer);
switch (c) {
case ' ':
case '\t':
break;
default:
lineBufferReset(&parser->buffer);
lineBufferAppendCharUnchecked(&parser->buffer, c);
}
parser->mode = h2Mode;
parser->state = normalState;
}
enterH3Mode(gemtextParser *parser, char c) {
lineBufferReset(&parser->buffer);
switch (c) {
case ' ':
case '\t':
break;
default:
lineBufferReset(&parser->buffer);
lineBufferAppendCharUnchecked(&parser->buffer, c);
}
parser->mode = h3Mode;
parser->state = normalState;
}
int parseLink(gemtextParser *parser, char c, gemtextLineQueue *lq) {
// todo
return 0;
}
int parsePreformatted(gemtextParser *parser, char c, gemtextLineQueue *lq) {
// todo
return 0;
}
int parseQuote(gemtextParser *parser, char c, gemtextLineQueue *lq) {
// todo
return 0;
}
int parseH1(gemtextParser *parser, char c, gemtextLineQueue *lq) {
// todo
return 0;
}
int parseH2(gemtextParser *parser, char c, gemtextLineQueue *lq) {
// todo
return 0;
}
int parseH3(gemtextParser *parser, char c, gemtextLineQueue *lq) {
2023-10-06 01:16:55 -04:00
// todo
return 0;
}
2023-10-08 11:59:17 -04:00
int parseList(gemtextParser *parser, char c, gemtextLineQueue *lq) {
2023-10-06 01:16:55 -04:00
// todo
return 0;
}
2023-10-08 11:59:17 -04:00
int parseNormal(gemtextParser *parser, char c, gemtextLineQueue *lq) {
int ret;
switch (parser->state) {
case lineStart:
switch (c) {
case '=':
parser->state = firstLinkChar;
break;
case '>':
parser->mode = quoteMode;
parser->state = lineStart;
break;
case '*':
parser->mode = listMode;
parser->state = normalState;
case '#':
parser->state = firstHashChar;
break;
case '`':
parser->state = firstBacktickChar;
break;
case '\n':
ret = gemtextParserSend(parser, normalLine, lq);
if (ret) return ret;
break;
default:
break;
}
break;
case firstLinkChar:
if (c == '>') {
parser->state = lineStart;
parser->mode = linkMode;
} else if (c == '\n') {
ret = gemtextParserSend(parser, normalLine, lq);
if (ret) return ret;
} else {
parser->state = normalState;
}
break;
case firstHashChar:
if (c == '#') {
parser->state = secondHashChar;
} else if (c == '\n') {
ret = gemtextParserSend(parser, normalLine, lq);
if (ret) return ret;
} else {
enterH1Mode(parser, c);
}
break;
case secondHashChar:
if (c == '#') {
parser->state = thirdHashChar;
} else if (c == '\n') {
ret = gemtextParserSend(parser, normalLine, lq);
if (ret) return ret;
} else {
enterH2Mode(parser, c);
}
break;
case thirdHashChar:
if (c == '\n') {
ret = gemtextParserSend(parser, normalLine, lq);
if (ret) return ret;
} else {
enterH3Mode(parser, c);
}
break;
case firstBacktickChar:
if (c == '\n') {
ret = gemtextParserSend(parser, normalLine, lq);
if (ret) return ret;
} else if (c == '`') {
parser->state = secondBacktickChar;
} else {
parser->state = normalState;
parser->mode = normalMode;
}
case secondBacktickChar:
if (c == '`') {
parser->state = thirdBacktickChar;
} else if (c == '\n') {
ret = gemtextParserSend(parser, normalLine, lq);
if (ret) return ret;
}
case thirdBacktickChar:
case normalState:
break;
}
return 0;
}
int parseGemtext(gemtextParser *parser, gemtextLineQueue *lq) {
2023-10-07 13:24:32 -04:00
char c;
int ret;
gemtextLine *line;
2023-10-08 11:59:17 -04:00
for (;;) {
2023-10-07 13:24:32 -04:00
ret = fread(&c, 1, 1, parser->stream);
2023-10-08 11:59:17 -04:00
if (ret == 1) {
ret = lineBufferAppendChar(&parser->buffer, c);
if (ret) {
logParseError(ret);
return ret;
}
} else {
2023-10-07 13:24:32 -04:00
line = calloc(1, sizeof(gemtextLine));
if (line == NULL) return errno;
line->lineType = endOfStream;
line->prev = line->next = NULL;
line->str = NULL;
gemtextLineQueuePush(lq, line);
2023-10-08 11:59:17 -04:00
break;
2023-10-07 13:24:32 -04:00
}
2023-10-08 11:59:17 -04:00
switch (parser->mode) {
case normalMode:
ret = parseNormal(parser, c, lq);
2023-10-07 13:24:32 -04:00
break;
2023-10-08 11:59:17 -04:00
case preformattedMode:
ret = parsePreformatted(parser, c, lq);
break;
case quoteMode:
ret = parseQuote(parser, c, lq);
break;
case linkMode:
ret = parseLink(parser, c, lq);
break;
case h1Mode:
ret = parseH1(parser, c, lq);
break;
case h2Mode:
ret = parseH2(parser, c, lq);
break;
case h3Mode:
ret = parseH3(parser, c, lq);
2023-10-07 13:24:32 -04:00
break;
2023-10-08 11:59:17 -04:00
case listMode:
ret = parseList(parser, c, lq);
break;
}
if (ret) {
logParseError(ret);
return ret;
2023-10-07 13:24:32 -04:00
}
}
2023-10-06 01:16:55 -04:00
return 0;
}