Ready for testing

This commit is contained in:
Nathan Fisher 2023-10-09 22:48:31 -04:00
parent 626ebaeb30
commit c6605630b5
2 changed files with 279 additions and 133 deletions

View file

@ -1,3 +1,4 @@
#include <assert.h> // assert
#include <errno.h> // errno #include <errno.h> // errno
#include <stddef.h> // NULL, size_t #include <stddef.h> // NULL, size_t
#include <stdio.h> // fclose #include <stdio.h> // fclose
@ -134,11 +135,79 @@ int lineBufferAppendString(lineBuffer *lb, char *c, size_t len) {
return ret; return ret;
} }
void lineBufferRewind(lineBuffer *lb) {
lb->len--;
lb->cursor--;
}
void lineBufferReset(lineBuffer *lb) { void lineBufferReset(lineBuffer *lb) {
lb->len = 0; lb->len = 0;
lb->cursor = lb->buf; lb->cursor = lb->buf;
} }
int gemtextParserSendPreformatted(gemtextParser *parser, gemtextLineQueue *lq) {
preformattedNode *node;
gemtextLine *line;
char *buf;
line = calloc(1, sizeof(gemtextLine));
if (line == NULL) return errno;
line->lineType = preformattedLine;
node = calloc(1, sizeof(preformattedNode));
if (node == NULL) return errno;
// back up our cursor four spaces and insert a lf char
parser->buffer.cursor -= 4;
parser->buffer.len -= 4;
lineBufferAppendCharUnchecked(&parser->buffer, '\n');
buf = strndup(parser->buffer.buf, parser->buffer.len);
if (buf == NULL) return errno;
node->altText = parser->altText;
parser->altText = NULL;
node->body = buf;
line->node = node;
gemtextLineQueuePush(lq, line);
lineBufferReset(&parser->buffer);
parser->state = lineStart;
parser->mode = normalMode;
return 0;
}
int gemtextParserSendLink(gemtextParser *parser, gemtextLineQueue *lq) {
gemtextLink *link;
gemtextLine *line;
char *url = NULL, *display = NULL;
link = calloc(1, sizeof(gemtextLink));
if (link == NULL) return errno;
link->display = link->url = NULL;
line = calloc(1, sizeof(gemtextLine));
if (line == NULL) {
free(link);
return errno;
}
if (parser->linkUrl == NULL) {
url = strndup(parser->buffer.buf, parser->buffer.len - 1);
} else {
url = parser->linkUrl;
display = strndup(parser->buffer.buf, parser->buffer.len);
if (display == NULL) {
free(link);
free(line);
return errno;
}
}
link->url = url;
link->display = display;
line->lineType = linkLine;
line->link = link;
gemtextLineQueuePush(lq, line);
lineBufferReset(&parser->buffer);
parser->state = lineStart;
parser->mode = normalMode;
parser->linkUrl = NULL;
return 0;
}
int gemtextParserSend(gemtextParser *parser, gemtextLineType lt, gemtextLineQueue *lq) { int gemtextParserSend(gemtextParser *parser, gemtextLineType lt, gemtextLineQueue *lq) {
gemtextLine *line; gemtextLine *line;
char *buf; char *buf;
@ -160,136 +229,199 @@ void logParseError(int err) {
//todo //todo
} }
gemtextLink* readLink(FILE *stream, lineBuffer *lb) { void switchMode(gemtextParser *parser, gemtextParserMode mode, char c) {
char c; lineBufferReset(&parser->buffer);
char *buf; switch (c) {
int ret = 0; case ' ':
gemtextLink *link = calloc(1, sizeof(gemtextLink)); case '\t':
parser->state = trimStart;
break;
default:
lineBufferReset(&parser->buffer);
lineBufferAppendCharUnchecked(&parser->buffer, c);
parser->state = normalState;
}
parser->mode = mode;
}
if (link == NULL) return NULL; void enterPreformattedMode(gemtextParser *parser) {
while (1) { parser->mode = preformattedMode;
fread(&c, 1, 1, stream); parser->state = preformattedAlt;
switch (c) { lineBufferReset(&parser->buffer);
case ' ': }
case '\t':
if (lb->len == 0) int parseLink(gemtextParser *parser, gemtextLineQueue *lq, char c) {
continue; int ret = 0;
buf = strndup(lb->buf, lb->len); char *buf = NULL;
link->url = buf;
break; assert(parser->mode == linkMode);
case '\n': switch (parser->state) {
if (lb->len == 0) { case lineStart:
free(link); if (c == ' ' || c == '\t') {
return NULL; lineBufferRewind(&parser->buffer);
} } else if (c == '\n') {
buf = strndup(lb->buf, lb->len); ret = gemtextParserSend(parser, normalLine, lq);
link->url = buf; } else {
return link; parser->state = normalState;
default: }
ret = lineBufferAppendChar(lb, c); break;
if (ret != 0) { case normalState:
free(link); if (c == ' ' || c == '\t') {
return NULL; buf = strndup(parser->buffer.buf, parser->buffer.len - 1);
if (buf == NULL) return errno;
parser->linkUrl = buf;
parser->state = linkDisplayStart;
lineBufferReset(&parser->buffer);
} else if (c == '\n') {
buf = strndup(parser->buffer.buf, parser->buffer.len);
if (buf == NULL) return errno;
ret = gemtextParserSendLink(parser, lq);
}
break;
case linkDisplayStart:
if (c == ' ' || c == '\t') {
lineBufferRewind(&parser->buffer);
} else if (c == '\n') {
ret = gemtextParserSendLink(parser, lq);
} else {
parser->state = linkDisplay;
}
break;
case linkDisplay:
if (c == '\n') {
ret = gemtextParserSendLink(parser, lq);
}
break;
default:
ret = 1;
break;
}
return ret;
}
int parsePreformatted(gemtextParser *parser, gemtextLineQueue *lq, char c) {
char *buf = NULL;
assert(parser->mode == preformattedMode);
switch (parser->state) {
case preformattedAlt:
if (c == '\n') {
parser->state = normalState;
if (parser->buffer.len > 0) {
buf = strncpy(buf, parser->buffer.buf, parser->buffer.len);
if (buf == NULL) return errno;
parser->altText = buf;
} }
} }
} break;
lineBufferReset(lb); case normalState:
while (1) { if (c == '\n') {
fread(&c, 1, 1, stream); parser->state = lineStart;
switch (c) { }
case '\n': break;
link->display = strndup(lb->buf, lb->len); case lineStart:
break; if (c == '\n') {
default: parser->state = firstBacktickChar;
ret = lineBufferAppendChar(lb, c); }
if (ret != 0) { break;
free(link->url); case firstBacktickChar:
free(link); if (c == '`') {
return NULL; parser->state = secondBacktickChar;
} } else {
parser->state = normalState;
}
break;
case secondBacktickChar:
if (c == '`') {
parser->state = thirdBacktickChar;
} else {
parser->state = normalState;
}
break;
case thirdBacktickChar:
if (c == '\n') {
gemtextParserSendPreformatted(parser, lq);
} else {
// We discard anything past the third backtick
parser->buffer.cursor--;
parser->buffer.len--;
} }
}
return link;
}
enterH1Mode(gemtextParser *parser, char c) {
lineBufferReset(&parser->buffer);
switch (c) {
case ' ':
case '\t':
break; break;
default: default:
lineBufferReset(&parser->buffer); return 1;
lineBufferAppendCharUnchecked(&parser->buffer, c);
} }
parser->mode = h1Mode; return 0;
parser->state = normalState;
} }
enterH2Mode(gemtextParser *parser, char c) { int parseQuote(gemtextParser *parser, gemtextLineQueue *lq, char c) {
lineBufferReset(&parser->buffer); int ret = 0;
switch (c) {
case ' ': switch (parser->state) {
case '\t': case lineStart:
if (c == '>') {
parser->state = trimStart;
} else {
parser->buffer.len--;
parser->buffer.cursor--;
ret = gemtextParserSend(parser, quoteLine, lq);
if (ret) return ret;
lineBufferAppendCharUnchecked(&parser->buffer, c);
parser->state = normalState;
}
break;
case normalState:
if (c == '\n') {
parser->state = lineStart;
}
break;
case trimStart:
if (c == ' ' || c == '\t') {
// rewind and trim the whitespace
parser->buffer.len--;
parser->buffer.cursor--;
} else if (c == '\n') {
ret = gemtextParserSend(parser, normalLine, lq);
} else {
parser->state = normalState;
}
break; break;
default: default:
lineBufferReset(&parser->buffer); // Should be unreachable
lineBufferAppendCharUnchecked(&parser->buffer, c); ret = 1;
break;
} }
parser->mode = h2Mode; return ret;
parser->state = normalState;
} }
enterH3Mode(gemtextParser *parser, char c) { int parseGeneric(gemtextParser *parser, gemtextLineQueue *lq, gemtextLineType lt, char c) {
lineBufferReset(&parser->buffer); int ret = 0;
switch (c) {
case ' ': switch (parser->state) {
case '\t': case lineStart:
if (c == ' ' || c == '\t') {
// rewind the cursor to trim the line start
parser->buffer.len--;
parser->buffer.cursor--;
} else if (c == '\n') {
ret = gemtextParserSend(parser, lt, lq);
} else {
parser->state = normalState;
}
break;
case normalState:
if (c == '\n') {
ret = gemtextParserSend(parser, h1Line, lq);
}
break; break;
default: default:
lineBufferReset(&parser->buffer); // Should be unreachable
lineBufferAppendCharUnchecked(&parser->buffer, c); ret = 1;
break;
} }
parser->mode = h3Mode; return ret;
parser->state = normalState;
} }
int parseLink(gemtextParser *parser, char c, gemtextLineQueue *lq) { int parseNormal(gemtextParser *parser, gemtextLineQueue *lq, char c) {
// 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; int ret;
switch (parser->state) { switch (parser->state) {
@ -321,8 +453,7 @@ int parseNormal(gemtextParser *parser, char c, gemtextLineQueue *lq) {
break; break;
case firstLinkChar: case firstLinkChar:
if (c == '>') { if (c == '>') {
parser->state = lineStart; switchMode(parser, linkMode, c);
parser->mode = linkMode;
} else if (c == '\n') { } else if (c == '\n') {
ret = gemtextParserSend(parser, normalLine, lq); ret = gemtextParserSend(parser, normalLine, lq);
if (ret) return ret; if (ret) return ret;
@ -337,7 +468,7 @@ int parseNormal(gemtextParser *parser, char c, gemtextLineQueue *lq) {
ret = gemtextParserSend(parser, normalLine, lq); ret = gemtextParserSend(parser, normalLine, lq);
if (ret) return ret; if (ret) return ret;
} else { } else {
enterH1Mode(parser, c); switchMode(parser, h1Mode, c);
} }
break; break;
case secondHashChar: case secondHashChar:
@ -347,7 +478,7 @@ int parseNormal(gemtextParser *parser, char c, gemtextLineQueue *lq) {
ret = gemtextParserSend(parser, normalLine, lq); ret = gemtextParserSend(parser, normalLine, lq);
if (ret) return ret; if (ret) return ret;
} else { } else {
enterH2Mode(parser, c); switchMode(parser, h2Mode, c);
} }
break; break;
case thirdHashChar: case thirdHashChar:
@ -355,7 +486,7 @@ int parseNormal(gemtextParser *parser, char c, gemtextLineQueue *lq) {
ret = gemtextParserSend(parser, normalLine, lq); ret = gemtextParserSend(parser, normalLine, lq);
if (ret) return ret; if (ret) return ret;
} else { } else {
enterH3Mode(parser, c); switchMode(parser, h3Mode, c);
} }
break; break;
case firstBacktickChar: case firstBacktickChar:
@ -370,14 +501,16 @@ int parseNormal(gemtextParser *parser, char c, gemtextLineQueue *lq) {
} }
case secondBacktickChar: case secondBacktickChar:
if (c == '`') { if (c == '`') {
parser->state = thirdBacktickChar; enterPreformattedMode(parser);
} else if (c == '\n') { } else if (c == '\n') {
ret = gemtextParserSend(parser, normalLine, lq); ret = gemtextParserSend(parser, normalLine, lq);
if (ret) return ret; if (ret) return ret;
} else {
parser->state = normalState;
parser->mode = normalMode;
} }
case thirdBacktickChar: default:
case normalState: break;
break;
} }
return 0; return 0;
} }
@ -406,28 +539,28 @@ int parseGemtext(gemtextParser *parser, gemtextLineQueue *lq) {
} }
switch (parser->mode) { switch (parser->mode) {
case normalMode: case normalMode:
ret = parseNormal(parser, c, lq); ret = parseNormal(parser, lq, c);
break; break;
case preformattedMode: case preformattedMode:
ret = parsePreformatted(parser, c, lq); ret = parsePreformatted(parser, lq, c);
break; break;
case quoteMode: case quoteMode:
ret = parseQuote(parser, c, lq); ret = parseQuote(parser, lq, c);
break; break;
case linkMode: case linkMode:
ret = parseLink(parser, c, lq); ret = parseLink(parser, lq, c);
break; break;
case h1Mode: case h1Mode:
ret = parseH1(parser, c, lq); ret = parseGeneric(parser, lq, h1Line, c);
break; break;
case h2Mode: case h2Mode:
ret = parseH2(parser, c, lq); ret = parseGeneric(parser, lq, h2Line, c);
break; break;
case h3Mode: case h3Mode:
ret = parseH3(parser, c, lq); ret = parseGeneric(parser, lq, h3Line, c);
break; break;
case listMode: case listMode:
ret = parseList(parser, c, lq); ret = parseGeneric(parser, lq, listLine, c);
break; break;
} }
if (ret) { if (ret) {

View file

@ -22,12 +22,16 @@ typedef enum {
lineStart, lineStart,
lineEnd, lineEnd,
firstLinkChar, firstLinkChar,
linkDisplayStart,
linkDisplay,
firstHashChar, firstHashChar,
secondHashChar, secondHashChar,
thirdHashChar, thirdHashChar,
firstBacktickChar, firstBacktickChar,
secondBacktickChar, secondBacktickChar,
thirdBacktickChar, thirdBacktickChar,
preformattedAlt,
trimStart,
normalState, normalState,
} gemtextParserState; } gemtextParserState;
@ -55,12 +59,20 @@ typedef struct {
char *display; char *display;
} gemtextLink; } gemtextLink;
typedef struct {
char *altText;
char *body;
} preformattedNode;
typedef struct { typedef struct {
FILE *stream; FILE *stream;
gemtextParserMode mode; gemtextParserMode mode;
gemtextParserState state; gemtextParserState state;
lineBuffer buffer; lineBuffer buffer;
char *linkUrl; union {
char *linkUrl;
char *altText;
};
} gemtextParser; } gemtextParser;
struct _gemtextLine { struct _gemtextLine {
@ -68,8 +80,9 @@ struct _gemtextLine {
struct _gemtextLine *prev; struct _gemtextLine *prev;
gemtextLineType lineType; gemtextLineType lineType;
union { union {
char *str; char *str;
gemtextLink *link; gemtextLink *link;
preformattedNode *node;
}; };
}; };