#include // errno #include // NULL, size_t #include // fclose #include // calloc, free #include // memcpy #include "gemtext-parser.h" 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; } gemtextParser* gemtextParserInit(FILE *stream) { gemtextParser *parser = calloc(1, sizeof(gemtextParser)); if (parser == NULL) return NULL; parser->stream = stream; parser->mode = normalMode; parser->state = lineStart; if (lineBufferInit(&parser->buffer) != 0) { free(parser); return NULL; } parser->linkUrl = NULL; return parser; } void gemtextParserDeinit(gemtextParser *parser) { fclose(parser->stream); free(parser->buffer.buf); if (parser->linkUrl != NULL) { free(parser->linkUrl); } 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; } 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 } gemtextLink* readLink(FILE *stream, lineBuffer *lb) { char c; char *buf; 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; buf = strndup(lb->buf, lb->len); link->url = buf; break; case '\n': if (lb->len == 0) { free(link); return NULL; } buf = strndup(lb->buf, lb->len); link->url = buf; 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; } 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) { // todo return 0; } int parseList(gemtextParser *parser, char c, gemtextLineQueue *lq) { // todo return 0; } 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) { char c; int ret; gemtextLine *line; for (;;) { ret = fread(&c, 1, 1, parser->stream); if (ret == 1) { ret = lineBufferAppendChar(&parser->buffer, c); if (ret) { logParseError(ret); return ret; } } else { line = calloc(1, sizeof(gemtextLine)); if (line == NULL) return errno; line->lineType = endOfStream; line->prev = line->next = NULL; line->str = NULL; gemtextLineQueuePush(lq, line); break; } switch (parser->mode) { case normalMode: ret = parseNormal(parser, c, lq); break; 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); break; case listMode: ret = parseList(parser, c, lq); break; } if (ret) { logParseError(ret); return ret; } } return 0; }