Ready for testing
This commit is contained in:
parent
626ebaeb30
commit
c6605630b5
2 changed files with 279 additions and 133 deletions
389
gemtext-parser.c
389
gemtext-parser.c
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue