/** \file gemtext-parser.h * \brief A fast Gemtext markup parser */ #ifndef GEMTEXT_PARSER_H #define GEMTEXT_PARSER_H 1 #include // pthread_mutex_t, pthread_cond_t #include // size_t #include // FILE #define LBUF_SIZE 512 ///< The default size of a lineBuffer /** The main modes which the parser can operate in */ typedef enum { normalMode, /**< A normal text line is being parsed, or the parser is still determining the line type */ preformattedMode, ///< A Preformatted block is being parsed quoteMode, ///< A Quote block is being parsed linkMode, ///< A hyperlink is being parsed h1Mode, ///< An H1 heading is being parsed h2Mode, ///< An H2 heading is being parsed h3Mode, ///< An H3 heading is being parsed listMode, ///< A list member is being parsed } gemtextParserMode; /** 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 { normalLine = 0, ///< A normal text line linkLine = 1, ///< A link line listLine = 2, ///< A list member h1Line = 3, ///< An H1 heading h2Line = 4, ///< An H2 heading h3Line = 5, ///< An H3 heading preformattedLine = 6, ///< A preformatted text block quoteLine = 7, ///< A Quote block endOfStream = 8, /**< Notifies the receiver that the stream is over and no more lines are to be expected */ } gemtextLineType; /** * 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 } preformattedNode; /** * The main Gemtext parser */ typedef struct { FILE *stream; /**< A stream of bytes to read gemtext from */ gemtextParserMode mode; /**< 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; struct _gemtextLine { struct _gemtextLine *next; ///< The next line in the queue struct _gemtextLine *prev; ///< The previous line in the queue gemtextLineType lineType; ///< Identifies the type of line union { char *str; ///< The text body of most line types gemtextLink *link; ///< The body of a link line preformattedNode *node; ///< The body and alt text of a preformatted block }; }; /** A Gemtext node */ typedef struct _gemtextLine gemtextLine; /** * 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 gemtextLine *head; ///< The oldest line in the queue gemtextLine *tail; ///< The newest line in the queue } gemtextLineQueue; /** * 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 gemtextLineQueue 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 queue The already allocated gemtextLineQueue */ int gemtextLineQueueInit(gemtextLineQueue *queue); /** * Pushes a gemtextLine into the queue. This function will not fail, but * can block if another thread holds the gemtextQueue's internal mutex. * \param queue The queue which will receive the gemtext line * \param line The gemtextLine to be queued */ void gemtextLineQueuePush(gemtextLineQueue *queue, gemtextLine *line); /** * Gets the oldest line inserted in the queue. This function will either * return a valid gemtextLine or block until one becomes available. * \param lq The queue from which we are attempting to pop a line */ gemtextLine* gemtextLineQueuePop(gemtextLineQueue *lq); /** * Attempts to get the oldest line inserted in the queue. If there are no lines * left in the queue, returns NULL. * \param lq The queue from which we are attempting to pop a line */ gemtextLine* gemtextLineQueueTryPop(gemtextLineQueue *lq); /** * Frees all memory associated with a gemtextLine structure * \param lq The gemtextLine to be de-allocated */ void gemtextLineDeinit(gemtextLine *line); /** * 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 gemtextLineQueue 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 lq A gemtextLineQueue which will receive gemtextLine elements as they are parsed */ int parseGemtext(gemtextParser *parser, gemtextLineQueue *lq); #endif