254 lines
9.6 KiB
C
254 lines
9.6 KiB
C
/** \file gemtext-parser.h
|
|
* \brief A fast Gemtext markup parser
|
|
*/
|
|
#ifndef GEMTEXT_PARSER_H
|
|
#define GEMTEXT_PARSER_H 1
|
|
|
|
#include <pthread.h> // pthread_mutex_t, pthread_cond_t
|
|
#include <stddef.h> // size_t
|
|
#include <stdio.h> // FILE
|
|
|
|
#define LBUF_SIZE 512 ///< The default size of a lineBuffer
|
|
|
|
/** An enumeration representing the state of the parsing action. These values
|
|
* are to be taken in context with the current gemtextParserMode */
|
|
typedef enum {
|
|
lineStart = 0, ///< The cursor is at the start of a new line
|
|
lineEnd = 1, ///< The cursor is at the end of a line
|
|
firstLinkChar = 2, ///< The first link character was the previous character
|
|
linkDisplayStart = 3, /**< The url of a link has been parsed and the cursor is at the
|
|
beginning of the display element */
|
|
linkDisplay = 4, ///< The link's display element is being parsed
|
|
firstHashChar = 5, ///< A Single '#' character has been encountered
|
|
secondHashChar = 6, ///< Two '#' characters have been encountered sequentially
|
|
thirdHashChar = 7, ///< Three '#' characters have been encountered sequentially
|
|
firstBacktickChar = 8, ///< A single '`' character has been encountered
|
|
secondBacktickChar = 9, ///< Two '`' characters have been encountered sequentially
|
|
thirdBacktickChar = 10, ///< Three '`' characters have been encountered sequentially
|
|
preformattedAlt = 11, ///< A Preformatted block's alt text is being parsed
|
|
trimStart = 12, ///< The *mode* is known and leading whitespace is being trimmed
|
|
normalState = 13, ///< The *mode* is known and normal parsing is occurring
|
|
} gemtextParserState;
|
|
|
|
/**
|
|
* An enum type representing the various line types in gemtext markup
|
|
*/
|
|
typedef enum {
|
|
unset = 0, ///< The node type has not yet been set
|
|
normalNode = 1, ///< A normal text line
|
|
linkNode = 2, ///< A link line
|
|
listNode = 3, ///< A list member
|
|
h1Node = 4, ///< An H1 heading
|
|
h2Node = 5, ///< An H2 heading
|
|
h3Node = 6, ///< An H3 heading
|
|
preformattedNode = 7, ///< A preformatted text block
|
|
quoteNode = 8, ///< A Quote block
|
|
endOfStream = 9, /**< Notifies the receiver that the stream is over and no
|
|
more lines are to be expected */
|
|
} gemtextNodeType;
|
|
|
|
/**
|
|
* A growable byte array
|
|
*/
|
|
typedef struct {
|
|
size_t capacity; ///< The current capacity of the internal buffer
|
|
size_t len; ///< The actual number of bytes currently in use
|
|
char *cursor; ///< A pointer to the next byte to be used in the internal buffer
|
|
char *buf; ///< A Pointer to the beginning of the internal buffer
|
|
} lineBuffer;
|
|
|
|
/**
|
|
* A Gemtext link element
|
|
*/
|
|
typedef struct {
|
|
char *url; ///< The url of the gemtext link
|
|
char *display; ///< Optional text to be displayed in lieu of the url
|
|
} gemtextLink;
|
|
|
|
/**
|
|
* A block of preformatted text
|
|
*/
|
|
typedef struct {
|
|
char *altText; /**< Some descriptive text to be read by screen readers if
|
|
this is ascii art */
|
|
char *body; ///< The body of the preformatted block
|
|
} preformattedBlock;
|
|
|
|
/**
|
|
* The main Gemtext parser
|
|
*/
|
|
typedef struct {
|
|
FILE *stream; /**< A stream of bytes to read gemtext from */
|
|
gemtextNodeType nodeType; /**< The current parsing mode */
|
|
gemtextParserState state; /**< The state of the parser within each mode */
|
|
lineBuffer buffer; /**< The internal buffer used to store bytes until
|
|
a gemtextLine is ready to be sent */
|
|
union {
|
|
char *linkUrl; /**< The url portion of a linkLine */
|
|
char *altText; /**< The alt text associated with a preformatted block */
|
|
};
|
|
} gemtextParser;
|
|
|
|
/** A Gemtext node */
|
|
struct _gemtextNode {
|
|
struct _gemtextNode *next; ///< The next line in the queue
|
|
struct _gemtextNode *prev; ///< The previous line in the queue
|
|
gemtextNodeType nodeType; ///< Identifies the type of line
|
|
union {
|
|
char *str; ///< The text body of most line types
|
|
gemtextLink *link; ///< The body of a link line
|
|
preformattedBlock *block; ///< The body and alt text of a preformatted block
|
|
};
|
|
};
|
|
|
|
/** A Gemtext node */
|
|
typedef struct _gemtextNode gemtextNode;
|
|
|
|
/**
|
|
* A fifo queue used to pass gemtextLine elements from the worker thread to the
|
|
* rendering thread.
|
|
*/
|
|
typedef struct {
|
|
pthread_cond_t cond; ///< Signals the rendering thread to wait for an incoming line
|
|
size_t count; ///< The number of elements currently in the queue
|
|
pthread_mutex_t mutex; ///< The lock ensuring exclusive access
|
|
gemtextNode *head; ///< The oldest line in the queue
|
|
gemtextNode *tail; ///< The newest line in the queue
|
|
} gemtextNodeQueue;
|
|
|
|
/**
|
|
* Initialize a lineBuffer struct to it's default values.
|
|
* ### Return values
|
|
* Returns 0 for success, 2 if memory allocation fails.
|
|
* \param lb A pointer to an already allocated lineBuffer
|
|
*/
|
|
int lineBufferInit(lineBuffer *lb);
|
|
|
|
/**
|
|
* Initialize a gemtextParser to it's default values.
|
|
* ### Return values
|
|
* Returns 0 upon success, 2 if memory allocation for the internal
|
|
* buffer fails.
|
|
* \param parser A pointer to an already allocated gemtextParser
|
|
* \param stream A FILE which we whose bytes will be read and parsed as gemtext lines
|
|
*/
|
|
int gemtextParserInit(gemtextParser *parser, FILE *stream);
|
|
|
|
/**
|
|
* Creates a new gemtextParser and initializes it to default values.
|
|
* If memory allocation fails a NULL pointer will be returned.
|
|
* \param stream The FILE stream which we will read and parse as gemtext lines
|
|
*/
|
|
gemtextParser* gemtextParserNew(FILE *stream);
|
|
|
|
/**
|
|
* Frees all memory associated with pointer members of this parser and closes
|
|
* the internal FILE stream.
|
|
* \param parser The gemtextParser to be finalized
|
|
*/
|
|
void gemtextParserDeinit(gemtextParser *parser);
|
|
|
|
/**
|
|
* Frees all memory associated with this gemtextParser.
|
|
* \param parser The gemtextParser to be freed
|
|
*/
|
|
void gemtextParserDestroy(gemtextParser *parser);
|
|
|
|
/**
|
|
* Initializes a gemtextNodeQueue with default values.
|
|
* ### Return values
|
|
* Returns 0 on success. If there is a failure initializing the internal
|
|
* mutex or condition variable, an error code is returned instead.
|
|
* \param nq The already allocated gemtextNodeQueue
|
|
*/
|
|
int gemtextNodeQueueInit(gemtextNodeQueue *nq);
|
|
|
|
/**
|
|
* Pushes a gemtextNode into the queue. This function will not fail, but
|
|
* can block if another thread holds the gemtextQueue's internal mutex.
|
|
* \param nq The queue which will receive the gemtext node
|
|
* \param node The gemtextNode to be queued
|
|
*/
|
|
void gemtextNodeQueuePush(gemtextNodeQueue *nq, gemtextNode *node);
|
|
|
|
/**
|
|
* Gets the oldest node inserted in the queue. This function will either
|
|
* return a valid gemtextNode or block until one becomes available.
|
|
* \param nq The queue from which we are attempting to pop a node
|
|
*/
|
|
gemtextNode* gemtextNodeQueuePop(gemtextNodeQueue *nq);
|
|
|
|
/**
|
|
* Attempts to get the oldest node inserted in the queue. If there are no nodes
|
|
* left in the queue, returns NULL.
|
|
* \param nq The queue from which we are attempting to pop a node
|
|
*/
|
|
gemtextNode* gemtextNodeQueueTryPop(gemtextNodeQueue *nq);
|
|
|
|
/**
|
|
* Frees all memory associated with a gemtextNode structure
|
|
* \param node The gemtextNode to be de-allocated
|
|
*/
|
|
void gemtextNodeDeinit(gemtextNode *node);
|
|
|
|
/**
|
|
* Extends the LineBuffer lb by len bytes.
|
|
* ### Return values
|
|
* Returns 0 upon success, or 2 if memory allocation fails.
|
|
* \param lb The buffer to be extended
|
|
* \param len The number of bytes to extend the buffer by
|
|
*/
|
|
int lineBufferExtend(lineBuffer *lb, size_t len);
|
|
|
|
/**
|
|
* Appends a character c to the lineBuffer lb. If there is no space left in the
|
|
* internal buffer, it will be re-allocated first.
|
|
* ### Return values
|
|
* Returns 0 for success, or 2 if memory allocation fails.
|
|
* \param lb The buffer we are appending to
|
|
* \param c The character to be appended to this buffer
|
|
*/
|
|
int lineBufferAppendChar(lineBuffer *lb, char c);
|
|
|
|
/**
|
|
* Appends a character c to the lineBuffer c without checking if there is space
|
|
* available first.
|
|
* > **Warning!** Due to the fact that this function is unchecked, it should
|
|
* > only be called if you are absolutely certain that there is space remaining
|
|
* > in the internal buffer, such as after calling lineBufferRewind to move the
|
|
* > cursor back by one character. Failure to follow this warning may result in
|
|
* > *buffer overflow* memory access violation.
|
|
*/
|
|
void lineBufferAppendCharUnchecked(lineBuffer *lb, char c);
|
|
|
|
/**
|
|
* Appends a string beginning at the pointer *c of len bytes to lineBuffer lb.
|
|
* ### Return values
|
|
* Returns 0 on success, or 2 for memory allocation errors.
|
|
* \param lb The buffer we are appending to
|
|
* \param c A pointer to an array of chars
|
|
* \param len The number of bytes to append from c
|
|
*/
|
|
int lineBufferAppendString(lineBuffer *lb, char *c, size_t len);
|
|
|
|
/**
|
|
* Rewinds the internal cursor pointer and count for lineBuffer lb by 1.
|
|
*/
|
|
void lineBufferRewind(lineBuffer *lb);
|
|
|
|
/**
|
|
* Resets the internal count of lineBuffer lb to 0 and moves it's cursor back
|
|
* to the start of the internal buffer.
|
|
*/
|
|
void lineBufferReset(lineBuffer *lb);
|
|
|
|
/**
|
|
* Parses gemtext into a series of nodes to be places in the gemtextNodeQueue lq.
|
|
* ### Return values
|
|
* Returns 0 on success, any other number is an error code
|
|
* \param parser A gemtextParser struct used to maintain state while parsing
|
|
* \param nq A gemtextNodeQueue which will receive gemtextLine elements as they are parsed
|
|
*/
|
|
int parseGemtext(gemtextParser *parser, gemtextNodeQueue *nq);
|
|
|
|
#endif
|